<commit_before>/* ========================================================================
 * Bootstrap: tooltip.js v3.3.2
 * http://getbootstrap.com/javascript/#tooltip
 * Inspired by the original jQuery.tipsy by Jason Frame
 * ========================================================================
 * Copyright 2011-2015 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */

+function ($) {
  'use strict';

  // TOOLTIP PUBLIC CLASS DEFINITION
  // ===============================

  var Tooltip = function (element, options) {
    this.type       = null
    this.options    = null
    this.enabled    = null
    this.timeout    = null
    this.hoverState = null
    this.$element   = null

    this.init('tooltip', element, options)
  }

  Tooltip.VERSION  = '3.3.2'

  Tooltip.TRANSITION_DURATION = 150

  Tooltip.DEFAULTS = {
    animation: true,
    placement: 'top',
    selector: false,
    template: '

',
    trigger: 'hover focus',
    title: '',
    delay: 0,
    html: false,
    container: false,
    viewport: {
      selector: 'body',
      padding: 0
    }
  }

  Tooltip.prototype.init = function (type, element, options) {
    this.enabled   = true
    this.type      = type
    this.$element  = $(element)
    this.options   = this.getOptions(options)
    this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)

    if (this.$element[0] instanceof document.constructor && !this.options.selector) {
      throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!');
    }

    var triggers = this.options.trigger.split(' ')

    for (var i = triggers.length; i--;) {
      var trigger = triggers[i]

      if (trigger == 'click') {
        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
      } else if (trigger != 'manual') {
        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'
        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'

        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
      }
    }

    this.options.selector ?
      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
      this.fixTitle()
  }

  Tooltip.prototype.getDefaults = function () {
    return Tooltip.DEFAULTS
  }

  Tooltip.prototype.getOptions = function (options) {
    options = $.extend({}, this.getDefaults(), this.$element.data(), options)

    if (options.delay && typeof options.delay == 'number') {
      options.delay = {
        show: options.delay,
        hide: options.delay
      }
    }

    return options
  }

  Tooltip.prototype.getDelegateOptions = function () {
    var options  = {}
    var defaults = this.getDefaults()

    this._options && $.each(this._options, function (key, value) {
      if (defaults[key] != value) options[key] = value
    })

    return options
  }

  Tooltip.prototype.enter = function (obj) {
    var self = obj instanceof this.constructor ?
      obj : $(obj.currentTarget).data('bs.' + this.type)

    if (self && self.$tip && self.$tip.is(':visible')) {
      self.hoverState = 'in'
      return
    }

    if (!self) {
      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
      $(obj.currentTarget).data('bs.' + this.type, self)
    }

    clearTimeout(self.timeout)

    self.hoverState = 'in'

    if (!self.options.delay || !self.options.delay.show) return self.show()

    self.timeout = setTimeout(function () {
      if (self.hoverState == 'in') self.show()
    }, self.options.delay.show)
  }

  Tooltip.prototype.leave = function (obj) {
    var self = obj instanceof this.constructor ?
      obj : $(obj.currentTarget).data('bs.' + this.type)

    if (!self) {
      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
      $(obj.currentTarget).data('bs.' + this.type, self)
    }

    clearTimeout(self.timeout)

    self.hoverState = 'out'

    if (!self.options.delay || !self.options.delay.hide) return self.hide()

    self.timeout = setTimeout(function () {
      if (self.hoverState == 'out') self.hide()
    }, self.options.delay.hide)
  }

  Tooltip.prototype.show = function () {
    var e = $.Event('show.bs.' + this.type)

    if (this.hasContent() && this.enabled) {
      this.$element.trigger(e)

      var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
      if (e.isDefaultPrevented() || !inDom) return
      var that = this

      var $tip = this.tip()

      var tipId = this.getUID(this.type)

      this.setContent()
      $tip.attr('id', tipId)
      this.$element.attr('aria-describedby', tipId)

      if (this.options.animation) $tip.addClass('fade')

      var placement = typeof this.options.placement == 'function' ?
        this.options.placement.call(this, $tip[0], this.$element[0]) :
        this.options.placement

      var autoToken = /\s?auto?\s?/i
      var autoPlace = autoToken.test(placement)
      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'

      $tip
        .detach()
        .css({ top: 0, left: 0, display: 'block' })
        .addClass(placement)
        .data('bs.' + this.type, this)

      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)

      var pos          = this.getPosition()
      var actualWidth  = $tip[0].offsetWidth
      var actualHeight = $tip[0].offsetHeight

      if (autoPlace) {
        var orgPlacement = placement
        var $container   = this.options.container ? $(this.options.container) : this.$element.parent()
        var containerDim = this.getPosition($container)

        placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top'    :
                    placement == 'top'    && pos.top    - actualHeight < containerDim.top    ? 'bottom' :
                    placement == 'right'  && pos.right  + actualWidth  > containerDim.width  ? 'left'   :
                    placement == 'left'   && pos.left   - actualWidth  < containerDim.left   ? 'right'  :
                    placement

        $tip
          .removeClass(orgPlacement)
          .addClass(placement)
      }

      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)

      this.applyPlacement(calculatedOffset, placement)

      var complete = function () {
        var prevHoverState = that.hoverState
        that.$element.trigger('shown.bs.' + that.type)
        that.hoverState = null

        if (prevHoverState == 'out') that.leave(that)
      }

      $.support.transition && this.$tip.hasClass('fade') ?
        $tip
          .one('bsTransitionEnd', complete)
          .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
        complete()
    }
  }

  Tooltip.prototype.applyPlacement = function (offset, placement) {
    var $tip   = this.tip()
    var width  = $tip[0].offsetWidth
    var height = $tip[0].offsetHeight

    // manually read margins because getBoundingClientRect includes difference
    var marginTop = parseInt($tip.css('margin-top'), 10)
    var marginLeft = parseInt($tip.css('margin-left'), 10)

    // we must check for NaN for ie 8/9
    if (isNaN(marginTop))  marginTop  = 0
    if (isNaN(marginLeft)) marginLeft = 0

    offset.top  = offset.top  + marginTop
    offset.left = offset.left + marginLeft

    // $.fn.offset doesn't round pixel values
    // so we use setOffset directly with our own function B-0
    $.offset.setOffset($tip[0], $.extend({
      using: function (props) {
        $tip.css({
          top: Math.round(props.top),
          left: Math.round(props.left)
        })
      }
    }, offset), 0)

    $tip.addClass('in')

    // check to see if placing tip in new offset caused the tip to resize itself
    var actualWidth  = $tip[0].offsetWidth
    var actualHeight = $tip[0].offsetHeight

    if (placement == 'top' && actualHeight != height) {
      offset.top = offset.top + height - actualHeight
    }

    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)

    if (delta.left) offset.left += delta.left
    else offset.top += delta.top

    var isVertical          = /top|bottom/.test(placement)
    var arrowDelta          = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
    var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'

    $tip.offset(offset)
    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
  }

  Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
    this.arrow()
      .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
      .css(isVertical ? 'top' : 'left', '')
  }

  Tooltip.prototype.setContent = function () {
    var $tip  = this.tip()
    var title = this.getTitle()

    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
    $tip.removeClass('fade in top bottom left right')
  }

  Tooltip.prototype.hide = function (callback) {
    var that = this
    var $tip = this.tip()
    var e    = $.Event('hide.bs.' + this.type)

    function complete() {
      if (that.hoverState != 'in') $tip.detach()
      that.$element
        .removeAttr('aria-describedby')
        .trigger('hidden.bs.' + that.type)
      callback && callback()
    }

    this.$element.trigger(e)

    if (e.isDefaultPrevented()) return

    $tip.removeClass('in')

    $.support.transition && this.$tip.hasClass('fade') ?
      $tip
        .one('bsTransitionEnd', complete)
        .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
      complete()

    this.hoverState = null

    return this
  }

  Tooltip.prototype.fixTitle = function () {
    var $e = this.$element
    if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {
      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
    }
  }

  Tooltip.prototype.hasContent = function () {
    return this.getTitle()
  }

  Tooltip.prototype.getPosition = function ($element) {
    $element   = $element || this.$element

    var el     = $element[0]
    var isBody = el.tagName == 'BODY'

    var elRect    = el.getBoundingClientRect()
    if (elRect.width == null) {
      // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
      elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
    }
    var elOffset  = isBody ? { top: 0, left: 0 } : $element.offset()
    var scroll    = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
    var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null

    return $.extend({}, elRect, scroll, outerDims, elOffset)
  }

  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2 } :
           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }

  }

  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
    var delta = { top: 0, left: 0 }
    if (!this.$viewport) return delta

    var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
    var viewportDimensions = this.getPosition(this.$viewport)

    if (/right|left/.test(placement)) {
      var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll
      var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
      if (topEdgeOffset < viewportDimensions.top) { // top overflow
        delta.top = viewportDimensions.top - topEdgeOffset
      } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
        delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
      }
    } else {
      var leftEdgeOffset  = pos.left - viewportPadding
      var rightEdgeOffset = pos.left + viewportPadding + actualWidth
      if (leftEdgeOffset < viewportDimensions.left) { // left overflow
        delta.left = viewportDimensions.left - leftEdgeOffset
      } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
        delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
      }
    }

    return delta
  }

  Tooltip.prototype.getTitle = function () {
    var title
    var $e = this.$element
    var o  = this.options

    title = $e.attr('data-original-title')
      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)

    return title
  }

  Tooltip.prototype.getUID = function (prefix) {
    do prefix += ~~(Math.random() * 1000000)
    while (document.getElementById(prefix))
    return prefix
  }

  Tooltip.prototype.tip = function () {
    return (this.$tip = this.$tip || $(this.options.template))
  }

  Tooltip.prototype.arrow = function () {
    return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
  }

  Tooltip.prototype.enable = function () {
    this.enabled = true
  }

  Tooltip.prototype.disable = function () {
    this.enabled = false
  }

  Tooltip.prototype.toggleEnabled = function () {
    this.enabled = !this.enabled
  }

  Tooltip.prototype.toggle = function (e) {
    var self = this
    if (e) {
      self = $(e.currentTarget).data('bs.' + this.type)
      if (!self) {
        self = new this.constructor(e.currentTarget, this.getDelegateOptions())
        $(e.currentTarget).data('bs.' + this.type, self)
      }
    }

    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
  }

  Tooltip.prototype.destroy = function () {
    var that = this
    clearTimeout(this.timeout)
    this.hide(function () {
      that.$element.off('.' + that.type).removeData('bs.' + that.type)
    })
  }

  // TOOLTIP PLUGIN DEFINITION
  // =========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.tooltip')
      var options = typeof option == 'object' && option

      if (!data && option == 'destroy') return
      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.tooltip

  $.fn.tooltip             = Plugin
  $.fn.tooltip.Constructor = Tooltip

  // TOOLTIP NO CONFLICT
  // ===================

  $.fn.tooltip.noConflict = function () {
    $.fn.tooltip = old
    return this
  }

}(jQuery);
<commit_msg>Tooltip: Avoid creating an unnecessary tooltip when `Tooltip#hide` is called<commit_after>/* ========================================================================
 * Bootstrap: tooltip.js v3.3.2
 * http://getbootstrap.com/javascript/#tooltip
 * Inspired by the original jQuery.tipsy by Jason Frame
 * ========================================================================
 * Copyright 2011-2015 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */

+function ($) {
  'use strict';

  // TOOLTIP PUBLIC CLASS DEFINITION
  // ===============================

  var Tooltip = function (element, options) {
    this.type       = null
    this.options    = null
    this.enabled    = null
    this.timeout    = null
    this.hoverState = null
    this.$element   = null

    this.init('tooltip', element, options)
  }

  Tooltip.VERSION  = '3.3.2'

  Tooltip.TRANSITION_DURATION = 150

  Tooltip.DEFAULTS = {
    animation: true,
    placement: 'top',
    selector: false,
    template: '

',
    trigger: 'hover focus',
    title: '',
    delay: 0,
    html: false,
    container: false,
    viewport: {
      selector: 'body',
      padding: 0
    }
  }

  Tooltip.prototype.init = function (type, element, options) {
    this.enabled   = true
    this.type      = type
    this.$element  = $(element)
    this.options   = this.getOptions(options)
    this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)

    if (this.$element[0] instanceof document.constructor && !this.options.selector) {
      throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!');
    }

    var triggers = this.options.trigger.split(' ')

    for (var i = triggers.length; i--;) {
      var trigger = triggers[i]

      if (trigger == 'click') {
        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
      } else if (trigger != 'manual') {
        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'
        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'

        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
      }
    }

    this.options.selector ?
      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
      this.fixTitle()
  }

  Tooltip.prototype.getDefaults = function () {
    return Tooltip.DEFAULTS
  }

  Tooltip.prototype.getOptions = function (options) {
    options = $.extend({}, this.getDefaults(), this.$element.data(), options)

    if (options.delay && typeof options.delay == 'number') {
      options.delay = {
        show: options.delay,
        hide: options.delay
      }
    }

    return options
  }

  Tooltip.prototype.getDelegateOptions = function () {
    var options  = {}
    var defaults = this.getDefaults()

    this._options && $.each(this._options, function (key, value) {
      if (defaults[key] != value) options[key] = value
    })

    return options
  }

  Tooltip.prototype.enter = function (obj) {
    var self = obj instanceof this.constructor ?
      obj : $(obj.currentTarget).data('bs.' + this.type)

    if (self && self.$tip && self.$tip.is(':visible')) {
      self.hoverState = 'in'
      return
    }

    if (!self) {
      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
      $(obj.currentTarget).data('bs.' + this.type, self)
    }

    clearTimeout(self.timeout)

    self.hoverState = 'in'

    if (!self.options.delay || !self.options.delay.show) return self.show()

    self.timeout = setTimeout(function () {
      if (self.hoverState == 'in') self.show()
    }, self.options.delay.show)
  }

  Tooltip.prototype.leave = function (obj) {
    var self = obj instanceof this.constructor ?
      obj : $(obj.currentTarget).data('bs.' + this.type)

    if (!self) {
      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
      $(obj.currentTarget).data('bs.' + this.type, self)
    }

    clearTimeout(self.timeout)

    self.hoverState = 'out'

    if (!self.options.delay || !self.options.delay.hide) return self.hide()

    self.timeout = setTimeout(function () {
      if (self.hoverState == 'out') self.hide()
    }, self.options.delay.hide)
  }

  Tooltip.prototype.show = function () {
    var e = $.Event('show.bs.' + this.type)

    if (this.hasContent() && this.enabled) {
      this.$element.trigger(e)

      var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
      if (e.isDefaultPrevented() || !inDom) return
      var that = this

      var $tip = this.tip()

      var tipId = this.getUID(this.type)

      this.setContent()
      $tip.attr('id', tipId)
      this.$element.attr('aria-describedby', tipId)

      if (this.options.animation) $tip.addClass('fade')

      var placement = typeof this.options.placement == 'function' ?
        this.options.placement.call(this, $tip[0], this.$element[0]) :
        this.options.placement

      var autoToken = /\s?auto?\s?/i
      var autoPlace = autoToken.test(placement)
      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'

      $tip
        .detach()
        .css({ top: 0, left: 0, display: 'block' })
        .addClass(placement)
        .data('bs.' + this.type, this)

      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)

      var pos          = this.getPosition()
      var actualWidth  = $tip[0].offsetWidth
      var actualHeight = $tip[0].offsetHeight

      if (autoPlace) {
        var orgPlacement = placement
        var $container   = this.options.container ? $(this.options.container) : this.$element.parent()
        var containerDim = this.getPosition($container)

        placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top'    :
                    placement == 'top'    && pos.top    - actualHeight < containerDim.top    ? 'bottom' :
                    placement == 'right'  && pos.right  + actualWidth  > containerDim.width  ? 'left'   :
                    placement == 'left'   && pos.left   - actualWidth  < containerDim.left   ? 'right'  :
                    placement

        $tip
          .removeClass(orgPlacement)
          .addClass(placement)
      }

      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)

      this.applyPlacement(calculatedOffset, placement)

      var complete = function () {
        var prevHoverState = that.hoverState
        that.$element.trigger('shown.bs.' + that.type)
        that.hoverState = null

        if (prevHoverState == 'out') that.leave(that)
      }

      $.support.transition && this.$tip.hasClass('fade') ?
        $tip
          .one('bsTransitionEnd', complete)
          .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
        complete()
    }
  }

  Tooltip.prototype.applyPlacement = function (offset, placement) {
    var $tip   = this.tip()
    var width  = $tip[0].offsetWidth
    var height = $tip[0].offsetHeight

    // manually read margins because getBoundingClientRect includes difference
    var marginTop = parseInt($tip.css('margin-top'), 10)
    var marginLeft = parseInt($tip.css('margin-left'), 10)

    // we must check for NaN for ie 8/9
    if (isNaN(marginTop))  marginTop  = 0
    if (isNaN(marginLeft)) marginLeft = 0

    offset.top  = offset.top  + marginTop
    offset.left = offset.left + marginLeft

    // $.fn.offset doesn't round pixel values
    // so we use setOffset directly with our own function B-0
    $.offset.setOffset($tip[0], $.extend({
      using: function (props) {
        $tip.css({
          top: Math.round(props.top),
          left: Math.round(props.left)
        })
      }
    }, offset), 0)

    $tip.addClass('in')

    // check to see if placing tip in new offset caused the tip to resize itself
    var actualWidth  = $tip[0].offsetWidth
    var actualHeight = $tip[0].offsetHeight

    if (placement == 'top' && actualHeight != height) {
      offset.top = offset.top + height - actualHeight
    }

    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)

    if (delta.left) offset.left += delta.left
    else offset.top += delta.top

    var isVertical          = /top|bottom/.test(placement)
    var arrowDelta          = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
    var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'

    $tip.offset(offset)
    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
  }

  Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
    this.arrow()
      .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
      .css(isVertical ? 'top' : 'left', '')
  }

  Tooltip.prototype.setContent = function () {
    var $tip  = this.tip()
    var title = this.getTitle()

    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
    $tip.removeClass('fade in top bottom left right')
  }

  Tooltip.prototype.hide = function (callback) {
    var that = this
    var $tip = $(this.$tip)
    var e    = $.Event('hide.bs.' + this.type)

    function complete() {
      if (that.hoverState != 'in') $tip.detach()
      that.$element
        .removeAttr('aria-describedby')
        .trigger('hidden.bs.' + that.type)
      callback && callback()
    }

    this.$element.trigger(e)

    if (e.isDefaultPrevented()) return

    $tip.removeClass('in')

    $.support.transition && $tip.hasClass('fade') ?
      $tip
        .one('bsTransitionEnd', complete)
        .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
      complete()

    this.hoverState = null

    return this
  }

  Tooltip.prototype.fixTitle = function () {
    var $e = this.$element
    if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {
      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
    }
  }

  Tooltip.prototype.hasContent = function () {
    return this.getTitle()
  }

  Tooltip.prototype.getPosition = function ($element) {
    $element   = $element || this.$element

    var el     = $element[0]
    var isBody = el.tagName == 'BODY'

    var elRect    = el.getBoundingClientRect()
    if (elRect.width == null) {
      // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
      elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
    }
    var elOffset  = isBody ? { top: 0, left: 0 } : $element.offset()
    var scroll    = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
    var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null

    return $.extend({}, elRect, scroll, outerDims, elOffset)
  }

  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2 } :
           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }

  }

  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
    var delta = { top: 0, left: 0 }
    if (!this.$viewport) return delta

    var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
    var viewportDimensions = this.getPosition(this.$viewport)

    if (/right|left/.test(placement)) {
      var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll
      var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
      if (topEdgeOffset < viewportDimensions.top) { // top overflow
        delta.top = viewportDimensions.top - topEdgeOffset
      } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
        delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
      }
    } else {
      var leftEdgeOffset  = pos.left - viewportPadding
      var rightEdgeOffset = pos.left + viewportPadding + actualWidth
      if (leftEdgeOffset < viewportDimensions.left) { // left overflow
        delta.left = viewportDimensions.left - leftEdgeOffset
      } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
        delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
      }
    }

    return delta
  }

  Tooltip.prototype.getTitle = function () {
    var title
    var $e = this.$element
    var o  = this.options

    title = $e.attr('data-original-title')
      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)

    return title
  }

  Tooltip.prototype.getUID = function (prefix) {
    do prefix += ~~(Math.random() * 1000000)
    while (document.getElementById(prefix))
    return prefix
  }

  Tooltip.prototype.tip = function () {
    return (this.$tip = this.$tip || $(this.options.template))
  }

  Tooltip.prototype.arrow = function () {
    return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
  }

  Tooltip.prototype.enable = function () {
    this.enabled = true
  }

  Tooltip.prototype.disable = function () {
    this.enabled = false
  }

  Tooltip.prototype.toggleEnabled = function () {
    this.enabled = !this.enabled
  }

  Tooltip.prototype.toggle = function (e) {
    var self = this
    if (e) {
      self = $(e.currentTarget).data('bs.' + this.type)
      if (!self) {
        self = new this.constructor(e.currentTarget, this.getDelegateOptions())
        $(e.currentTarget).data('bs.' + this.type, self)
      }
    }

    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
  }

  Tooltip.prototype.destroy = function () {
    var that = this
    clearTimeout(this.timeout)
    this.hide(function () {
      that.$element.off('.' + that.type).removeData('bs.' + that.type)
    })
  }

  // TOOLTIP PLUGIN DEFINITION
  // =========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.tooltip')
      var options = typeof option == 'object' && option

      if (!data && option == 'destroy') return
      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.tooltip

  $.fn.tooltip             = Plugin
  $.fn.tooltip.Constructor = Tooltip

  // TOOLTIP NO CONFLICT
  // ===================

  $.fn.tooltip.noConflict = function () {
    $.fn.tooltip = old
    return this
  }

}(jQuery);
<|endoftext|>


<commit_before>/*!
* Turkuaz Framework v1.0
* www.turkuazcss.com
* MIT Lisansi (https://raw.githubusercontent.com/TurkuazCss/Framework/master/LICENSE)
*/

/*!
*    Turkuaz Framework - JavaScript Kod Duzeni
*
*   1. Etiket
*   2. Form
*   3. Menu
*   4. Notlar
*   5. Sayfa Konumu
*   6. Modal
*   7. Medya Modal
*
*/

/* ============= 1. Etiket */

$("a.etiket.kapat").click(function(e){ e.preventDefault() })
$("a.etiket.kapat").append("&times")
.end()
.find("span.kaldir").click(function(){
    $(this).parents("a.etiket.kapat").addClass("gizle")
})

/* ============= 2. Form */

$formEtiketleri = ".tr-input,.tr-checkbox,.tr-radio,.tr-textarea"
$placeholder = ".tr-input,.tr-textarea"

placeholder()

$($formEtiketleri).focus(function(){
    $(this).addClass("aktif dolu")
}).focusout(function(){
    if($(this).val() == ""){
        $(this).removeClass("dolu")
    }
    placeholder()
    $(this).removeClass("aktif")
})

function placeholder(){
    $($placeholder).each(function(){
        var placeholder = $(this).attr("placeholder")
        if(placeholder !== undefined && placeholder !== "") {
            $(this).addClass("dolu")
        }
    })
}

/* ============= 3. Menu */

//Mobil Menu
$("nav.menu.mobil").on("click", "img.logo", function(){
    $(this).hide()
    .parents("nav.menu.mobil").animate({left:"0"})
    var overlay = $("

").addClass("tr-karart menu")
    $("body").append(overlay)
})

$("body").on("click", ".tr-karart.menu", function(){
    $(this).remove()
    $("nav.menu.mobil").animate({left:"-240px"})
    .find("img.logo").delay(400).fadeIn()
})

$("nav.menu ul li").find("ul li a").parent().parent().parent().addClass("acilir")

$("body").on("click", "nav.menu.mobil ul li.acilir", function(){
    var yukseklik = $(this).find("li").length * 45
    $(this).find("ul").css({
        "height": yukseklik + "px"
    })
    .slideToggle("500")
    .parent().siblings(".acilir")
    .find("ul:visible").slideToggle("500")
})

function mobil(){
    if ($(window).width() <= 1000) {
        $("nav.menu.mobil").addClass("mobilmenu")
        $(".tr-karart.menu").removeClass("gizle")
    }
    else {
        $("nav.menu.mobil").removeClass("mobilmenu")
        $(".tr-karart.menu").addClass("gizle")
    }
    $("nav.menu ul li.acilir ul").each(function(){
        $(this).hide("600")
    })
}

mobil()
$(window).resize(function() { mobil() })

// Tab Menu
;(function($){
    $.fn.tab = function(ayarlar){
        var obj = $.extend({
            "aktifSinifi" : "aktif",
            "icerikSinifi" : "icerik",
            "aktifSekme" : 1,
            "tema" : ""
        }, ayarlar)
        return this.each(function() {
            $(this).addClass("tr-tab " + obj.tema)
            .find("nav a").eq(obj.aktifSekme - 1).addClass(obj.aktifSinifi)
            .parents(".tr-tab").find("." + obj.icerikSinifi).addClass("tr-icerik gizle")
            .eq(obj.aktifSekme - 1).removeClass("gizle")
            $(this).find("nav a").click(function(){
                var index = $(this).index()
                $(this).addClass(obj.aktifSinifi)
                .siblings().removeClass(obj.aktifSinifi)
                .parents(".tr-tab").find("." + obj.icerikSinifi).addClass("gizle")
                .eq(index).removeClass("gizle")
            })
        })
    }
})(jQuery)

// Filtre Menu
$(".tr-filtre [data-target=hepsi]").addClass("aktif")
$(".tr-filtre").on("click", "nav a", function(){
    var target = $(this).data("target")
    $(this).addClass("aktif")
    .siblings().removeClass("aktif")
    .end()
    .parents(".tr-filtre").find(".tr.icerik").removeClass("gizle")
    .not("." + target).addClass("gizle")
    if(target == "hepsi" || target == "hepsi aktif") {
        $(this).parents(".tr-filtre").find(".icerik").removeClass("gizle")
    }
})

// Akordiyon MenuWW
;(function($){
    $.fn.akordiyon = function(ayarlar){
        var obj = $.extend({
            "aktifSinifi" : "aktif",
            "baslikSinifi" : "baslik",
            "icerikSinifi" : "icerik",
            "aktifSekme" : 1,
            "sure" : 200,
            "gecikme" : 0,
            "tema" : ""
        }, ayarlar)
        return this.each(function() {
            $sekmeSayisi = $("." + obj.baslikSinifi, this).length
            if (Math.abs(obj.aktifSekme) > $sekmeSayisi){
                obj.aktifSekme = $sekmeSayisi
            }
            $(this).addClass("tr-akordiyon " + obj.tema)
            .find("." + obj.icerikSinifi).delay(obj.gecikme).slideUp(obj.sure)
            .eq(obj.aktifSekme-1).slideDown(obj.sure)
            .end().end().find("." + obj.baslikSinifi).eq(obj.aktifSekme-1).addClass("aktif")
            $(this).find("." + obj.baslikSinifi).click(function(e){
                e.preventDefault()
                var index = $(this).index("." + obj.baslikSinifi)
                $(this).toggleClass(obj.aktifSinifi).siblings("."+ obj.baslikSinifi).removeClass(obj.aktifSinifi)
                .end().next().delay(obj.gecikme).slideToggle(obj.sure)
                .end().next().delay(obj.gecikme).siblings("."+ obj.icerikSinifi).slideUp(obj.sure)
            })
        })
    }
})(jQuery)

/* ============= 4. Notlar */

$(".tr-not").append("&times")

$(".tr-not").on("click", "span.kaldir", function(){
    $(this).parents(".tr-not").addClass("gizle")
})

/* ============= 5. Sayfa Konumu */

$(".tr-numara").on("click", "a.pasif,a.aktif", function(e){ e.preventDefault() })

/* ============= 6. Modal */

;(function($) {
    $.fn.modal = function(ayarlar) {
        var obj = $.extend({
            "autofocus" : true,
            "backgroundcolor" : "#000",
            "close" : false,
            "closeConnent" : "&times",
            "closeTime" : 100,
            "opacity" : .5,
            "overlay" : "tr-modal-karart",
            "openTime" : 500,
            "size" : "orta"
        }, ayarlar)
        return this.each(function() {
            $("body").find('[data-modal]').hide()
            $("body").on("click", "a.modal", function(e) {
                e.preventDefault()
                var href = $(this).attr("href")
                var modal = $("body").find('[data-modal=' + href + ']')
                var overlay = $("

").addClass("tr-karart " + obj.overlay)
                overlay.css({ "background-color": obj.backgroundcolor, "opacity": obj.opacity })
                modal.addClass(obj.size).fadeIn(obj.openTime).scrollTop(0)
                if ($(".tr-karart." + obj.overlay).length == 0) { $("body").append(overlay) }
                if (obj.close) { $("body").append("" + obj.closeConnent + "") }
                if (obj.autofocus) { modal.find(".tr-input:visible:first").focus() }
                $("body").on("click", ".modal-kapat, .tr-karart." + obj.overlay + ", .kapatButon", function(e) {
                    e.preventDefault()
                    modal.fadeOut(obj.closeTime)
                    .end().find("." + obj.overlay + ", .kapatButon").remove()
                })
            })
        })
    }
})(jQuery)

/* ============= 7. Medya Modal */

;(function($) {
    $.fn.medya = function(ayarlar) {
        var obj = $.extend({
            "backgroundcolor" : "#333",
            "height" : "90vh",
            "opacity" : 1,
            "overlay" : "tr-medya-karart"
        }, ayarlar)
        return this.each(function(e,index) {
            $(this).addClass("tr-medya")
            $(window).scroll(function() { medyaKapat() })
            $(document).keyup(function(e) { if (e.keyCode === 27) { medyaKapat() } })
            if ($(index).hasClass("aktif")) { medyaKapat() }
            $("body").on("click", ".tr-medya", function(e) {
                e.preventDefault()
                $(this).addClass("aktif")
                .css({ "max-height": obj.height,
                "left": "50%", "position": "fixed", "top": "50%", "transform": "translate(-50%, -50%)", "z-index": "1001" })
                var overlay = $("

").addClass("tr-karart " + obj.overlay)
                overlay.css({ "background-color": obj.backgroundcolor, "opacity": obj.opacity })
                if ($(".tr-karart." + obj.overlay).length == 0) {
                    $("body").append(overlay)
                    .end().find("nav.menu.mobil > img.logo").addClass("gizle")
                }
            })
        })
    }
})(jQuery)

function medyaKapat() {
    $(".tr-medya.aktif").removeClass("aktif")
    .css({ "height": "", "left": "", "max-height": "", "max-width": "", "position": "", "top": "", "transform": "", "z-index": "" })
    $("body").find(".tr-karart").remove()
    .end().find("nav.menu.mobil > img.logo").removeClass("gizle")
}

$(".tr-medya").medya()
<commit_msg>jQuery'deki Css Düzenlemeleri<commit_after>/*!
* Turkuaz Framework v1.0
* www.turkuazcss.com
* MIT Lisansi (https://raw.githubusercontent.com/TurkuazCss/Framework/master/LICENSE)
*/

/*!
*    Turkuaz Framework - JavaScript Kod Duzeni
*
*   1. Etiket
*   2. Form
*   3. Menu
*   4. Notlar
*   5. Sayfa Konumu
*   6. Modal
*   7. Medya Modal
*
*/

/* ============= 1. Etiket */

$("a.etiket.kapat").click(function(e){ e.preventDefault() })
$("a.etiket.kapat").append("&times")
.end()
.find("span.kaldir").click(function(){
    $(this).parents("a.etiket.kapat").addClass("gizle")
})

/* ============= 2. Form */

$formEtiketleri = ".tr-input,.tr-checkbox,.tr-radio,.tr-textarea"
$placeholder = ".tr-input,.tr-textarea"

placeholder()

$($formEtiketleri).focus(function(){
    $(this).addClass("aktif dolu")
}).focusout(function(){
    if($(this).val() == ""){
        $(this).removeClass("dolu")
    }
    placeholder()
    $(this).removeClass("aktif")
})

function placeholder(){
    $($placeholder).each(function(){
        var placeholder = $(this).attr("placeholder")
        if(placeholder !== undefined && placeholder !== "") {
            $(this).addClass("dolu")
        }
    })
}

/* ============= 3. Menu */

//Mobil Menu
$("nav.menu.mobil").on("click", "img.logo", function(){
    $(this).hide()
    .parents("nav.menu.mobil").animate({left:"0"})
    var overlay = $("

").addClass("tr-karart menu")
    $("body").append(overlay)
})

$("body").on("click", ".tr-karart.menu", function(){
    $(this).remove()
    $("nav.menu.mobil").animate({left:"-240px"})
    .find("img.logo").delay(400).fadeIn()
})

$("nav.menu ul li").find("ul li a").parent().parent().parent().addClass("acilir")

$("body").on("click", "nav.menu.mobil ul li.acilir", function(){
    var yukseklik = $(this).find("li").length * 45
    $(this).find("ul").css({
        "height": yukseklik + "px"
    })
    .slideToggle("500")
    .parent().siblings(".acilir")
    .find("ul:visible").slideToggle("500")
})

function mobil(){
    if ($(window).width() <= 1000) {
        $("nav.menu.mobil").addClass("mobilmenu")
        $(".tr-karart.menu").removeClass("gizle")
    }
    else {
        $("nav.menu.mobil").removeClass("mobilmenu")
        $(".tr-karart.menu").addClass("gizle")
    }
    $("nav.menu ul li.acilir ul").each(function(){
        $(this).hide("600")
    })
}

mobil()
$(window).resize(function() { mobil() })

// Tab Menu
;(function($){
    $.fn.tab = function(ayarlar){
        var obj = $.extend({
            "aktifSinifi" : "aktif",
            "icerikSinifi" : "icerik",
            "aktifSekme" : 1,
            "tema" : ""
        }, ayarlar)
        return this.each(function() {
            $(this).addClass("tr-tab " + obj.tema)
            .find("nav a").eq(obj.aktifSekme - 1).addClass(obj.aktifSinifi)
            .parents(".tr-tab").find("." + obj.icerikSinifi).addClass("tr-icerik gizle")
            .eq(obj.aktifSekme - 1).removeClass("gizle")
            $(this).find("nav a").click(function(){
                var index = $(this).index()
                $(this).addClass(obj.aktifSinifi)
                .siblings().removeClass(obj.aktifSinifi)
                .parents(".tr-tab").find("." + obj.icerikSinifi).addClass("gizle")
                .eq(index).removeClass("gizle")
            })
        })
    }
})(jQuery)

// Filtre Menu
$(".tr-filtre [data-target=hepsi]").addClass("aktif")
$(".tr-filtre").on("click", "nav a", function(){
    var target = $(this).data("target")
    $(this).addClass("aktif")
    .siblings().removeClass("aktif")
    .end()
    .parents(".tr-filtre").find(".tr.icerik").removeClass("gizle")
    .not("." + target).addClass("gizle")
    if(target == "hepsi" || target == "hepsi aktif") {
        $(this).parents(".tr-filtre").find(".icerik").removeClass("gizle")
    }
})

// Akordiyon MenuWW
;(function($){
    $.fn.akordiyon = function(ayarlar){
        var obj = $.extend({
            "aktifSinifi" : "aktif",
            "baslikSinifi" : "baslik",
            "icerikSinifi" : "icerik",
            "aktifSekme" : 1,
            "sure" : 200,
            "gecikme" : 0,
            "tema" : ""
        }, ayarlar)
        return this.each(function() {
            $sekmeSayisi = $("." + obj.baslikSinifi, this).length
            if (Math.abs(obj.aktifSekme) > $sekmeSayisi){
                obj.aktifSekme = $sekmeSayisi
            }
            $(this).addClass("tr-akordiyon " + obj.tema)
            .find("." + obj.icerikSinifi).delay(obj.gecikme).slideUp(obj.sure)
            .eq(obj.aktifSekme-1).slideDown(obj.sure)
            .end().end().find("." + obj.baslikSinifi).eq(obj.aktifSekme-1).addClass("aktif")
            $(this).find("." + obj.baslikSinifi).click(function(e){
                e.preventDefault()
                var index = $(this).index("." + obj.baslikSinifi)
                $(this).toggleClass(obj.aktifSinifi).siblings("."+ obj.baslikSinifi).removeClass(obj.aktifSinifi)
                .end().next().delay(obj.gecikme).slideToggle(obj.sure)
                .end().next().delay(obj.gecikme).siblings("."+ obj.icerikSinifi).slideUp(obj.sure)
            })
        })
    }
})(jQuery)

/* ============= 4. Notlar */

$(".tr-not").append("&times")

$(".tr-not").on("click", "span.kaldir", function(){
    $(this).parents(".tr-not").addClass("gizle")
})

/* ============= 5. Sayfa Konumu */

$(".tr-numara").on("click", "a.pasif,a.aktif", function(e){ e.preventDefault() })

/* ============= 6. Modal */

;(function($) {
    $.fn.modal = function(ayarlar) {
        var obj = $.extend({
            "autofocus" : true,
            "backgroundcolor" : "#000",
            "close" : false,
            "closeConnent" : "&times",
            "closeTime" : 100,
            "opacity" : .5,
            "overlay" : "tr-modal-karart",
            "openTime" : 500,
            "size" : "orta"
        }, ayarlar)
        return this.each(function() {
            $("body").find('[data-modal]').hide()
            $("body").on("click", "a.modal", function(e) {
                e.preventDefault()
                var href = $(this).attr("href")
                var modal = $("body").find('[data-modal=' + href + ']')
                var overlay = $("

").addClass("tr-karart " + obj.overlay)
                overlay.css({
                    "background-color": obj.backgroundcolor,
                    "opacity": obj.opacity
                })
                modal.addClass(obj.size).fadeIn(obj.openTime).scrollTop(0)
                if ($(".tr-karart." + obj.overlay).length == 0) { $("body").append(overlay) }
                if (obj.close) { $("body").append("" + obj.closeConnent + "") }
                if (obj.autofocus) { modal.find(".tr-input:visible:first").focus() }
                $("body").on("click", ".modal-kapat, .tr-karart." + obj.overlay + ", .kapatButon", function(e) {
                    e.preventDefault()
                    modal.fadeOut(obj.closeTime)
                    .end().find("." + obj.overlay + ", .kapatButon").remove()
                })
            })
        })
    }
})(jQuery)

/* ============= 7. Medya Modal */

;(function($) {
    $.fn.medya = function(ayarlar) {
        var obj = $.extend({
            "backgroundcolor" : "#333",
            "height" : "90vh",
            "opacity" : 1,
            "overlay" : "tr-medya-karart"
        }, ayarlar)
        return this.each(function(index,item) {
            $(this).addClass("tr-medya")
            $(window).scroll(function() { medyaKapat() })
            $(document).keyup(function(e) { if (e.keyCode === 27) { medyaKapat() } })
            if ($(this).hasClass("aktif")) { medyaKapat() }
            $("body").on("click", ".tr-medya", function(e) {
                e.preventDefault()
                $(this).addClass("aktif")
                .css({
                    "max-height": obj.height,
                    "left": "50%",
                    "position": "fixed",
                    "top": "50%",
                    "transform": "translate(-50%, -50%)",
                    "z-index": "1001"
                })
                var overlay = $("

").addClass("tr-karart " + obj.overlay)
                overlay.css({
                    "background-color": obj.backgroundcolor,
                    "opacity": obj.opacity
                })
                if ($(".tr-karart." + obj.overlay).length == 0) {
                    $("body").append(overlay)
                    .end().find("nav.menu.mobil > img.logo").addClass("gizle")
                }
            })
        })
    }
})(jQuery)

function medyaKapat() {
    $(".tr-medya.aktif").removeClass("aktif")
    .css({
        "height": "",
        "left": "",
        "max-height": "",
        "max-width": "",
        "position": "",
        "top": "",
        "transform": "",
        "z-index": ""
    })
    $("body").find(".tr-karart").remove()
    .end().find("nav.menu.mobil > img.logo").removeClass("gizle")
}

$(".tr-medya").medya()
<|endoftext|>


<commit_before>Version = { id: '97f41ad' };
<commit_msg>Force bump version<commit_after>Version = { id: '' };
<|endoftext|>


<commit_before>import React from 'react';
import Spotlight from '../../dist/index';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';

export default class Demo extends React.Component {

  constructor(props) {
    super(props);

    this.start = this.start.bind(this);
    this.state = {
      playing: false,
      x: 0,
      y: 0,
      radius: 75,
      color: '#d74793',
      borderColor: '#1976d2'
    };
  }

  start() {
    this.setStatePromise({ playing: true, ...this.getCoordinates('.btn-try') })
      .then(() => this.sleep(800))
      .then(() => this.setStatePromise({ radius: 200, ...this.getCoordinates('.code-block .source') }))
      .then(() => this.sleep(2000))
      .then(() => this.setStatePromise({ radius: 100, ...this.getCoordinates('.link-view-docs') }))
      .then(() => this.sleep(1500))
      .then(() => this.setStatePromise({ color: '#1976d2', borderColor: 'rgba(0, 50, 225, .5)', text: 'Hello world!' }))
      .then(() => this.sleep(2000))
      .then(() => this.setStatePromise({ playing: false }));
  }

  /* The following are some convenience methods to make chaining the animation more convenient */

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  setStatePromise(state) {
    this.setState(state);
    return Promise.resolve();
  }

  getCoordinates(selector) {
    const domEl = document.querySelector(selector);
    if (!domEl) return {};

    const rect = document.querySelector(selector).getBoundingClientRect();
    return { x: rect.left + (rect.width / 2), y: rect.top + (rect.height / 2) };
  }

  render() {
    // Note: the transition group and wrapper div aren't necessary.
    // They're just here to make the demo feel smoother
    return (

        <button onClick={this.start} className="btn btn-try">Try it out!</button>

        <ReactCSSTransitionGroup
          transitionName="fade"
          transitionEnterTimeout={500}
          transitionLeaveTimeout={300}>
          {this.state.playing && (

              <Spotlight
                x={this.state.x}
                y={this.state.y}
                radius={this.state.radius}
                color={this.state.color}
                responsive
                animSpeed={1000}
                borderColor={this.state.borderColor}
                borderWidth={10}>

{this.state.text}

              </Spotlight>

          )}
        </ReactCSSTransitionGroup>

    );
  }
}
<commit_msg>Use outerStyles in example<commit_after>import React from 'react';
import Spotlight from '../../dist/index';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';

export default class Demo extends React.Component {

  constructor(props) {
    super(props);

    this.start = this.start.bind(this);
    this.state = {
      playing: false,
      x: 0,
      y: 0,
      radius: 75,
      color: '#d74793',
      borderColor: '#1976d2'
    };
  }

  start() {
    this.setStatePromise({ playing: true, ...this.getCoordinates('.btn-try') })
      .then(() => this.sleep(800))
      .then(() => this.setStatePromise({ radius: 200, ...this.getCoordinates('.code-block .source') }))
      .then(() => this.sleep(2000))
      .then(() => this.setStatePromise({ radius: 100, ...this.getCoordinates('.link-view-docs') }))
      .then(() => this.sleep(1500))
      .then(() => this.setStatePromise({ color: '#1976d2', borderColor: 'rgba(0, 50, 225, .5)', text: 'Hello world!' }))
      .then(() => this.sleep(2000))
      .then(() => this.setStatePromise({ playing: false }));
  }

  /* The following are some convenience methods to make chaining the animation more convenient */

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  setStatePromise(state) {
    this.setState(state);
    return Promise.resolve();
  }

  getCoordinates(selector) {
    const domEl = document.querySelector(selector);
    if (!domEl) return {};

    const rect = document.querySelector(selector).getBoundingClientRect();
    return { x: rect.left + (rect.width / 2), y: rect.top + (rect.height / 2) };
  }

  render() {
    // Note: the transition group and wrapper div aren't necessary.
    // They're just here to make the demo feel smoother
    return (

        <button onClick={this.start} className="btn btn-try">Try it out!</button>

        <ReactCSSTransitionGroup
          transitionName="fade"
          transitionEnterTimeout={500}
          transitionLeaveTimeout={300}>
          {this.state.playing && (

              <Spotlight
                x={this.state.x}
                y={this.state.y}
                radius={this.state.radius}
                color={this.state.color}
                outerStyles={{ zIndex: 0 }}
                responsive
                animSpeed={1000}
                borderColor={this.state.borderColor}
                borderWidth={10}>

{this.state.text}

              </Spotlight>

          )}
        </ReactCSSTransitionGroup>

    );
  }
}
<|endoftext|>


<commit_before><commit_msg>Tests<commit_after><|endoftext|>


<commit_before>import { app } from 'electron'
import jetpack from 'fs-jetpack'

import { ENVIRONMENT, IS_PRODUCTION, IS_TESTING, IS_DEVELOPMENT } from '#config'

const withEnvronment = () => {
  if (!IS_PRODUCTION) {
    const userDataPath = app.getPath('userData')
    const nextDataPath = `${userDataPath} (${ENVIRONMENT})`
    if (IS_TESTING) {
      jetpack.remove(nextDataPath)
    }
    app.setPath('userData', nextDataPath)
  }

  if (IS_DEVELOPMENT) {
    app.commandLine.appendSwitch('remote-debugging-port', '9223')
    // require('electron-debug')({showDevTools: true})
    require('electron-context-menu')({})
  }
}

export default withEnvronment
<commit_msg>build: bring accurate userData folder name in development mode<commit_after>import { app } from 'electron'
import jetpack from 'fs-jetpack'
import path from 'path'

import { ENVIRONMENT, IS_PRODUCTION, IS_TESTING, IS_DEVELOPMENT } from '#config'

const withEnvronment = () => {
  const userDataPath = app.getPath('userData')
  const nextDataPath = path.resolve(
    userDataPath, `../Pathephone${!IS_PRODUCTION ? ` (${ENVIRONMENT})` : ''}`
  )
  if (IS_TESTING) {
    jetpack.remove(nextDataPath)
  }
  app.setPath('userData', nextDataPath)

  if (IS_DEVELOPMENT) {
    app.commandLine.appendSwitch('remote-debugging-port', '9223')
    // require('electron-debug')({showDevTools: true})
    require('electron-context-menu')({})
  }
}

export default withEnvronment
<|endoftext|>


<commit_before>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 */

'use strict';

declare module react {
  declare export var DOM: any;
  declare export var PropTypes: ReactPropTypes;
  declare export var version: string;

  declare export function checkPropTypes<V>(
    propTypes: any,
    values: V,
    location: string,
    componentName: string,
    getStack: ?() => ?string,
  ): void;

  declare export var createClass: React$CreateClass;
  declare export function createContext<T>(
    defaultValue: T,
    calculateChangedBits: ?(a: T, b: T) => number,
  ): React$Context<T>;
  declare export var createElement: React$CreateElement;
  declare export var cloneElement: React$CloneElement;
  declare export function createFactory<ElementType: React$ElementType>(
    type: ElementType,
  ): React$ElementFactory<ElementType>;
  declare export function createRef<T>(): {|current: null | T|};

  declare export function isValidElement(element: any): boolean;

  declare export var Component: typeof React$Component;
  declare export var PureComponent: typeof React$PureComponent;
  declare export type StatelessFunctionalComponent<
    P,
  > = React$StatelessFunctionalComponent<P>;
  declare export type ComponentType<-P> = React$ComponentType<P>;
  declare export type AbstractComponent<
    -Config,
    +Instance = mixed,
  > = React$AbstractComponent<Config, Instance>;
  declare export type MixedElement = React$MixedElement;
  declare export type ElementType = React$ElementType;
  // eslint-disable-next-line lint/flow-react-element
  declare export type Element<+C> = React$Element<C>;
  declare export var Fragment: React$FragmentType;
  declare export type Key = React$Key;
  declare export type Ref<C> = React$Ref<C>;
  declare export type Node = React$Node;
  declare export type TransportObject = React$TransportObject;
  declare export type TransportValue = React$TransportValue;
  declare export type Context<T> = React$Context<T>;
  declare export type Portal = React$Portal;
  declare export var ConcurrentMode: ({
    children?: React$Node,
    ...
  }) => React$Node; // 16.7+
  declare export var StrictMode: ({children?: React$Node, ...}) => React$Node;

  declare export var Suspense: React$ComponentType<{
    children?: React$Node,
    fallback?: React$Node,
    ...
  }>; // 16.6+

  declare export type ElementProps<C> = React$ElementProps<C>;
  declare export type ElementConfig<C> = React$ElementConfig<C>;
  declare export type ElementRef<C> = React$ElementRef<C>;
  declare export type Config<Props, DefaultProps> = React$Config<
    Props,
    DefaultProps,
  >;

  declare export type ChildrenArray<+T> = $ReadOnlyArray<ChildrenArray<T>> | T;
  declare export var Children: {
    map<T, U>(
      children: ChildrenArray<T>,
      fn: (child: $NonMaybeType<T>, index: number) => U,
      thisArg?: mixed,
    ): Array<$NonMaybeType<U>>,
    forEach<T>(
      children: ChildrenArray<T>,
      fn: (child: T, index: number) => mixed,
      thisArg?: mixed,
    ): void,
    count(children: ChildrenArray<any>): number,
    only<T>(children: ChildrenArray<T>): $NonMaybeType<T>,
    toArray<T>(children: ChildrenArray<T>): Array<$NonMaybeType<T>>,
    ...
  };

  declare export function forwardRef<Config, Instance>(
    render: (
      props: Config,
      ref: {current: null | Instance, ...} | ((null | Instance) => mixed),
    ) => React$Node,
  ): React$AbstractComponent<Config, Instance>;

  declare export function memo<Config, Instance = mixed>(
    component: React$AbstractComponent<Config, Instance>,
    equal?: (Config, Config) => boolean,
  ): React$AbstractComponent<Config, Instance>;

  declare export function lazy<Config, Instance = mixed>(
    component: () => Promise<{
      default: React$AbstractComponent<Config, Instance>,
      ...
    }>,
  ): React$AbstractComponent<Config, Instance>;

  declare type MaybeCleanUpFn = void | (() => void);

  declare export function useContext<T>(context: React$Context<T>): T;

  declare export function useState<S>(
    initialState: (() => S) | S,
  ): [S, ((S => S) | S) => void];

  declare type Dispatch<A> = (A) => void;

  declare export function useReducer<S, A>(
    reducer: (S, A) => S,
    initialState: S,
  ): [S, Dispatch<A>];

  declare export function useReducer<S, A>(
    reducer: (S, A) => S,
    initialState: S,
    init: void,
  ): [S, Dispatch<A>];

  declare export function useReducer<S, A, I>(
    reducer: (S, A) => S,
    initialArg: I,
    init: (I) => S,
  ): [S, Dispatch<A>];

  declare export function useRef<T>(initialValue: T): {|current: T|};

  declare export function useDebugValue(value: any): void;

  declare export function useEffect(
    create: () => MaybeCleanUpFn,
    inputs?: ?$ReadOnlyArray<mixed>,
  ): void;

  declare export function useLayoutEffect(
    create: () => MaybeCleanUpFn,
    inputs?: ?$ReadOnlyArray<mixed>,
  ): void;

  declare export function useCallback<
    T: (...args: $ReadOnlyArray<empty>) => mixed,
  >(
    callback: T,
    inputs: ?$ReadOnlyArray<mixed>,
  ): T;

  declare export function useMemo<T>(
    create: () => T,
    inputs: ?$ReadOnlyArray<mixed>,
  ): T;

  declare export function useImperativeHandle<T>(
    ref: {current: T | null, ...} | ((inst: T | null) => mixed) | null | void,
    create: () => T,
    inputs: ?$ReadOnlyArray<mixed>,
  ): void;

  declare export function useDeferredValue<T>(
    value: T,
    config: TimeoutConfig | void | null,
  ): T;

  declare export function useTransition(): [boolean, (() => void) => void];

  declare export function startTransition(() => void): void;

  declare export type Interaction = {
    name: string,
    timestamp: number,
    ...
  };

  declare type ProfilerOnRenderFnType = (
    id: string,
    phase: 'mount' | 'update',
    actualDuration: number,
    baseDuration: number,
    startTime: number,
    commitTime: number,
    interactions: Set<Interaction>,
  ) => void;

  declare export var Profiler: React$AbstractComponent<
    {|
      children?: React$Node,
      id: string,
      onRender: ProfilerOnRenderFnType,
    |},
    void,
  >;

  declare type TimeoutConfig = {|
    timeoutMs: number,
  |};

  declare export default {|
    +DOM: typeof DOM,
    +PropTypes: typeof PropTypes,
    +version: typeof version,
    +checkPropTypes: typeof checkPropTypes,
    +memo: typeof memo,
    +lazy: typeof lazy,
    +createClass: typeof createClass,
    +createContext: typeof createContext,
    +createElement: typeof createElement,
    +cloneElement: typeof cloneElement,
    +createFactory: typeof createFactory,
    +createRef: typeof createRef,
    +forwardRef: typeof forwardRef,
    +isValidElement: typeof isValidElement,
    +Component: typeof Component,
    +PureComponent: typeof PureComponent,
    +Fragment: React$FragmentType,
    +Children: typeof Children,
    +ConcurrentMode: typeof ConcurrentMode,
    +StrictMode: typeof StrictMode,
    +Profiler: typeof Profiler,
    +Suspense: typeof Suspense,
    +useContext: typeof useContext,
    +useState: typeof useState,
    +useReducer: typeof useReducer,
    +useRef: typeof useRef,
    +useEffect: typeof useEffect,
    +useLayoutEffect: typeof useLayoutEffect,
    +useCallback: typeof useCallback,
    +useMemo: typeof useMemo,
    +useImperativeHandle: typeof useImperativeHandle,
    +useTransition: typeof useTransition,
    +useDeferredValue: typeof useDeferredValue,
    +startTransition: typeof startTransition,
  |};
}
<commit_msg>Update React flow types<commit_after>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 */

'use strict';

// TODO: Remove after upgrading the flow version. This was added because we changed
// the types of useTransition, startTranstion, and useDeferredValue used only in test.
declare module react {
  declare export var DOM: any;
  declare export var PropTypes: ReactPropTypes;
  declare export var version: string;

  declare export function checkPropTypes<V>(
    propTypes: any,
    values: V,
    location: string,
    componentName: string,
    getStack: ?() => ?string,
  ): void;

  declare export var createClass: React$CreateClass;
  declare export function createContext<T>(
    defaultValue: T,
    calculateChangedBits: ?(a: T, b: T) => number,
  ): React$Context<T>;
  declare export var createElement: React$CreateElement;
  declare export var cloneElement: React$CloneElement;
  declare export function createFactory<ElementType: React$ElementType>(
    type: ElementType,
  ): React$ElementFactory<ElementType>;
  declare export function createRef<T>(): {|current: null | T|};

  declare export function isValidElement(element: any): boolean;

  declare export var Component: typeof React$Component;
  declare export var PureComponent: typeof React$PureComponent;
  declare export type StatelessFunctionalComponent<
    P,
  > = React$StatelessFunctionalComponent<P>;
  declare export type ComponentType<-P> = React$ComponentType<P>;
  declare export type AbstractComponent<
    -Config,
    +Instance = mixed,
  > = React$AbstractComponent<Config, Instance>;
  declare export type MixedElement = React$MixedElement;
  declare export type ElementType = React$ElementType;
  // eslint-disable-next-line lint/flow-react-element
  declare export type Element<+C> = React$Element<C>;
  declare export var Fragment: React$FragmentType;
  declare export type Key = React$Key;
  declare export type Ref<C> = React$Ref<C>;
  declare export type Node = React$Node;
  declare export type TransportObject = React$TransportObject;
  declare export type TransportValue = React$TransportValue;
  declare export type Context<T> = React$Context<T>;
  declare export type Portal = React$Portal;
  declare export var ConcurrentMode: ({
    children?: React$Node,
    ...
  }) => React$Node; // 16.7+
  declare export var StrictMode: ({children?: React$Node, ...}) => React$Node;

  declare export var Suspense: React$ComponentType<{
    children?: React$Node,
    fallback?: React$Node,
    ...
  }>; // 16.6+

  declare export type ElementProps<C> = React$ElementProps<C>;
  declare export type ElementConfig<C> = React$ElementConfig<C>;
  declare export type ElementRef<C> = React$ElementRef<C>;
  declare export type Config<Props, DefaultProps> = React$Config<
    Props,
    DefaultProps,
  >;

  declare export type ChildrenArray<+T> = $ReadOnlyArray<ChildrenArray<T>> | T;
  declare export var Children: {
    map<T, U>(
      children: ChildrenArray<T>,
      fn: (child: $NonMaybeType<T>, index: number) => U,
      thisArg?: mixed,
    ): Array<$NonMaybeType<U>>,
    forEach<T>(
      children: ChildrenArray<T>,
      fn: (child: T, index: number) => mixed,
      thisArg?: mixed,
    ): void,
    count(children: ChildrenArray<any>): number,
    only<T>(children: ChildrenArray<T>): $NonMaybeType<T>,
    toArray<T>(children: ChildrenArray<T>): Array<$NonMaybeType<T>>,
    ...
  };

  declare export function forwardRef<Config, Instance>(
    render: (
      props: Config,
      ref: {current: null | Instance, ...} | ((null | Instance) => mixed),
    ) => React$Node,
  ): React$AbstractComponent<Config, Instance>;

  declare export function memo<Config, Instance = mixed>(
    component: React$AbstractComponent<Config, Instance>,
    equal?: (Config, Config) => boolean,
  ): React$AbstractComponent<Config, Instance>;

  declare export function lazy<Config, Instance = mixed>(
    component: () => Promise<{
      default: React$AbstractComponent<Config, Instance>,
      ...
    }>,
  ): React$AbstractComponent<Config, Instance>;

  declare type MaybeCleanUpFn = void | (() => void);

  declare export function useContext<T>(context: React$Context<T>): T;

  declare export function useState<S>(
    initialState: (() => S) | S,
  ): [S, ((S => S) | S) => void];

  declare type Dispatch<A> = (A) => void;

  declare export function useReducer<S, A>(
    reducer: (S, A) => S,
    initialState: S,
  ): [S, Dispatch<A>];

  declare export function useReducer<S, A>(
    reducer: (S, A) => S,
    initialState: S,
    init: void,
  ): [S, Dispatch<A>];

  declare export function useReducer<S, A, I>(
    reducer: (S, A) => S,
    initialArg: I,
    init: (I) => S,
  ): [S, Dispatch<A>];

  declare export function useRef<T>(initialValue: T): {|current: T|};

  declare export function useDebugValue(value: any): void;

  declare export function useEffect(
    create: () => MaybeCleanUpFn,
    inputs?: ?$ReadOnlyArray<mixed>,
  ): void;

  declare export function useLayoutEffect(
    create: () => MaybeCleanUpFn,
    inputs?: ?$ReadOnlyArray<mixed>,
  ): void;

  declare export function useCallback<
    T: (...args: $ReadOnlyArray<empty>) => mixed,
  >(
    callback: T,
    inputs: ?$ReadOnlyArray<mixed>,
  ): T;

  declare export function useMemo<T>(
    create: () => T,
    inputs: ?$ReadOnlyArray<mixed>,
  ): T;

  declare export function useImperativeHandle<T>(
    ref: {current: T | null, ...} | ((inst: T | null) => mixed) | null | void,
    create: () => T,
    inputs: ?$ReadOnlyArray<mixed>,
  ): void;

  declare export function useDeferredValue<T>(value: T): T;

  declare export function useTransition(): [boolean, (() => void) => void];

  declare export function startTransition(() => void): void;

  declare export type Interaction = {
    name: string,
    timestamp: number,
    ...
  };

  declare type ProfilerOnRenderFnType = (
    id: string,
    phase: 'mount' | 'update',
    actualDuration: number,
    baseDuration: number,
    startTime: number,
    commitTime: number,
    interactions: Set<Interaction>,
  ) => void;

  declare export var Profiler: React$AbstractComponent<
    {|
      children?: React$Node,
      id: string,
      onRender: ProfilerOnRenderFnType,
    |},
    void,
  >;

  declare type TimeoutConfig = {|
    timeoutMs: number,
  |};

  declare export default {|
    +DOM: typeof DOM,
    +PropTypes: typeof PropTypes,
    +version: typeof version,
    +checkPropTypes: typeof checkPropTypes,
    +memo: typeof memo,
    +lazy: typeof lazy,
    +createClass: typeof createClass,
    +createContext: typeof createContext,
    +createElement: typeof createElement,
    +cloneElement: typeof cloneElement,
    +createFactory: typeof createFactory,
    +createRef: typeof createRef,
    +forwardRef: typeof forwardRef,
    +isValidElement: typeof isValidElement,
    +Component: typeof Component,
    +PureComponent: typeof PureComponent,
    +Fragment: React$FragmentType,
    +Children: typeof Children,
    +ConcurrentMode: typeof ConcurrentMode,
    +StrictMode: typeof StrictMode,
    +Profiler: typeof Profiler,
    +Suspense: typeof Suspense,
    +useContext: typeof useContext,
    +useState: typeof useState,
    +useReducer: typeof useReducer,
    +useRef: typeof useRef,
    +useEffect: typeof useEffect,
    +useLayoutEffect: typeof useLayoutEffect,
    +useCallback: typeof useCallback,
    +useMemo: typeof useMemo,
    +useImperativeHandle: typeof useImperativeHandle,
    +useTransition: typeof useTransition,
    +useDeferredValue: typeof useDeferredValue,
    +startTransition: typeof startTransition,
  |};
}
<|endoftext|>


<commit_before>// Holds one line's worth of data on a multi-line chart
var QueryModel = Backbone.Model.extend({
  // url: '/data',

  // Technically the fetching of data from server should happen in here

  initialize: function(queryObj){
    this.startDate = queryObj.startDate;
    this.endDate = queryObj.endDate;
    this.source = queryObj.source;
    this.keyword = queryObj.keyword;

    console.log('the start is ', this.startDate);

    // declare some variables that will be used when
    // we handleResponseData below
    this.responseData = [];
    this.articles = [];
    this.frequencyCounts = {};
    this.totalSentiments = {};
    this.averages = {};
    this.maxSentiment = 0;
    this.minSentiment = 0;

    // figure out what url to ping
    // example url:
    // "/data?startDate=20101214&endDate=20150114&source=newyorktimes&keyword=BP",
    var startDateURLFormat = this.startDate.getFullYear() + '' +
      this.startDate.getMonth() + '' +
      this.startDate.getDate();
    var endDateURLFormat = this.endDate.getFullYear() + '' +
      this.endDate.getMonth() + '' +
      this.endDate.getDate();
    var sourceURLFormat = this.source.replace(/\s+/g, '').toLowerCase();
    var keywordURLFormat = this.keyword.replace(/\s+/g, '') //.toLowerCase();

    this.url =
      '/data?startDate=' + startDateURLFormat +
      '&endDate=' + endDateURLFormat +
      '&source=' + sourceURLFormat +
      '&keyword=' + keywordURLFormat;
    console.log(this.url);
  },

  queryServer: function(){
    var scope = this;
    $.ajax({
      url: scope.url
    })
    .done(function( newData  ) {
      console.log('receiving data: ', newData);
      scope.set({
        responseData: newData,
      });
    });
    return this;
  },

  handleResponseData: function(){
    // calculate frequency counts and averages
    var scope = this;
    var maxSentiment = 0;
    var minSentiment = 0;

    var frequencyTally = {};
    var totalSentiment = {};
    var sentiment = {};

    var timeBucket = 'year';

    var articles = this.get('responseData').map(function(obj){
      return _.pick(obj, 'published', 'sentiment', 'url', 'headline');
    });

    // could vary this function if time period is not '1 year'
    // could use a more generic word like 'timeSpan' insted of 'year'
    articles.forEach(function(article){
      var date = new Date(article['published']);
      article['displayDate'] = date.toDateString();
      if (timeBucket = 'year'){
        article['timeBucket'] = date.getFullYear();
      }
      article['year'] = date.getFullYear();
      // article['month'] = date.getMonth();
      // article['year'] = year;
      // console.log('date:', date);
      article['date'] = date;
    });

    articles.forEach(function(article){
      var tBucket = article['timeBucket'];
      // var month = article['month'];
      var sentiment = article['sentiment'];
      // if (sentiment > maxSentiment){
      //   maxSentiment = sentiment;
      // } else if (sentiment < minSentiment){
      //   minSentiment = sentiment;
      // }
      if (!frequencyTally.hasOwnProperty(tBucket)){
        frequencyTally[tBucket] = 0;
        totalSentiment[tBucket] = 0;
      }
      frequencyTally[tBucket] = frequencyTally[tBucket] + 1;
      totalSentiment[tBucket] = totalSentiment[tBucket] + sentiment;

    });

    // put summary data into array format to graph it
    var summaryDataPoints = _.reduce(frequencyTally, function(memo, tally, tBucket){
      var dataPoint = {};
      dataPoint.count = tally;
      dataPoint.tBucket = tBucket;
      var midPeriodDate;
      if (timeBucket === 'year'){
        midPeriodDate = new Date(tBucket + "-07-01");
      }
      dataPoint.date = new Date(Math.min(scope.endDate, midPeriodDate));
      dataPoint.sentiment = totalSentiment[ tBucket ] / tally;
      return memo.concat( [dataPoint] );
    }, [] );

    this.set('articles', articles);
    this.set('maxSentiment', maxSentiment);
    this.set('minSentiment', minSentiment);
    this.set('summaryDataPoints', summaryDataPoints);
  },

}); <commit_msg>Allow option to group averages by quarter<commit_after>// Holds one line's worth of data on a multi-line chart
var QueryModel = Backbone.Model.extend({
  // url: '/data',

  // Technically the fetching of data from server should happen in here

  initialize: function(queryObj){
    this.startDate = queryObj.startDate;
    this.endDate = queryObj.endDate;
    this.source = queryObj.source;
    this.keyword = queryObj.keyword;

    console.log('the start is ', this.startDate);

    // declare some variables that will be used when
    // we handleResponseData below
    this.responseData = [];
    this.articles = [];
    this.frequencyCounts = {};
    this.totalSentiments = {};
    this.averages = {};
    this.maxSentiment = 0;
    this.minSentiment = 0;

    // figure out what url to ping
    // example url:
    // "/data?startDate=20101214&endDate=20150114&source=newyorktimes&keyword=BP",
    var startDateURLFormat = this.startDate.getFullYear() + '' +
      this.startDate.getMonth() + '' +
      this.startDate.getDate();
    var endDateURLFormat = this.endDate.getFullYear() + '' +
      this.endDate.getMonth() + '' +
      this.endDate.getDate();
    var sourceURLFormat = this.source.replace(/\s+/g, '').toLowerCase();
    var keywordURLFormat = this.keyword.replace(/\s+/g, '') //.toLowerCase();

    this.url =
      '/data?startDate=' + startDateURLFormat +
      '&endDate=' + endDateURLFormat +
      '&source=' + sourceURLFormat +
      '&keyword=' + keywordURLFormat;
    console.log(this.url);
  },

  queryServer: function(){
    var scope = this;
    $.ajax({
      url: scope.url
    })
    .done(function( newData  ) {
      console.log('receiving data: ', newData);
      scope.set({
        responseData: newData,
      });
    });
    return this;
  },

  handleResponseData: function(){
    // calculate frequency counts and averages
    var scope = this;
    var maxSentiment = 0;
    var minSentiment = 0;

    var frequencyTally = {};
    var totalSentiment = {};
    var sentiment = {};

    var timeBucket = 'quarter';

    console.log('averaging data points by ' + timeBucket)

    var articles = this.get('responseData').map(function(obj){
      return _.pick(obj, 'published', 'sentiment', 'url', 'headline');
    });

    articles.forEach(function(article){
      var date = new Date(article['published']);
      article['displayDate'] = date.toDateString();
      var year = date.getFullYear();
      if (timeBucket === 'year'){
        article['timeBucket'] = year + "-07-01";
      } else if (timeBucket === 'quarter'){
        var month = (Math.floor(date.getMonth()/3)) * 3 + 1;
        if (month.length === 1){
          month = '0' + month;
        }
        console.log('month:', month);
        article['timeBucket'] = year + '-' + month + '-' + '15';
      } else if (timeBucket === 'month'){
        var month = (date.getMonth() + 1) + '';
        if (month.length === 1){
          month = '0' + month;
        }
        console.log('month:', month);
        article['timeBucket'] = year + '-' + month + '-' + '15';
      }
      // date.getMonth();
      // article['year'] = year;
      // console.log('date:', date);
      article['date'] = date;
    });

    articles.forEach(function(article){
      var tBucket = article['timeBucket'];
      // var month = article['month'];
      var sentiment = article['sentiment'];
      // if (sentiment > maxSentiment){
      //   maxSentiment = sentiment;
      // } else if (sentiment < minSentiment){
      //   minSentiment = sentiment;
      // }
      if (!frequencyTally.hasOwnProperty(tBucket)){
        frequencyTally[tBucket] = 0;
        totalSentiment[tBucket] = 0;
      }
      frequencyTally[tBucket] = frequencyTally[tBucket] + 1;
      totalSentiment[tBucket] = totalSentiment[tBucket] + sentiment;

    });

    // put summary data into array format to graph it
    var summaryDataPoints = _.reduce(frequencyTally, function(memo, tally, tBucket){
      var dataPoint = {};
      dataPoint.count = tally;
      dataPoint.tBucket = tBucket;
      var midPeriodDate;
      // if (timeBucket === 'year'){
        midPeriodDate = new Date(tBucket);
      // }
      dataPoint.date = new Date(Math.min(scope.endDate, midPeriodDate));
      dataPoint.sentiment = totalSentiment[ tBucket ] / tally;
      return memo.concat( [dataPoint] );
    }, [] );

    this.set('articles', articles);
    this.set('maxSentiment', maxSentiment);
    this.set('minSentiment', minSentiment);
    this.set('summaryDataPoints', summaryDataPoints);
  },

}); <|endoftext|>


<commit_before>/*
 * We have a few competing goals:
 *
 * First, be as responsive as possible.  Preload nearby post HTML and their images.
 *
 * If data in a post page changes, eg. if the user votes, then coming back to the page
 * later should retain the changes.  This means either requesting the page again, or
 * retaining the document node and reusing it, so we preserve the changes that were
 * made in-place.
 *
 * Don't use too much memory.  If we keep every document node in memory as we use it,
 * the images will probably be kept around too.  Release older nodes, so the browser
 * is more likely to release images that havn't been used in a while.
 *
 * We do the following:
 * - When we load a new post, it's formatted and its scripts are evaluated normally.
 * - When we're replacing the displayed post, its node is stashed away in a node cache.
 * - If we come back to the post while it's in the node cache, we'll use the node directly.
 * - HTML and images for posts are preloaded.  We don't use a simple mechanism like
 *   Preload.preload_raw, because Opera's caching is broken for XHR and it'll always
 *   do a slow revalidation.
 * - We don't depend on browser caching for HTML.  That would require us to expire a
 *   page when we switch away from it if we've made any changes (eg. voting), so we
 *   don't pull an out-of-date page next time.  This is slower, and would require us
 *   to be careful about expiring the cache.
 */

BrowserView = function(container)
{
  this.container = container;

  /* The post that we currently want to display.  This will be either one of the
   * current html_preloads, or be the displayed_post_id. */
  this.wanted_post_id = null;

  /* The post that's currently actually being displayed. */
  this.displayed_post_id = null;

  this.current_ajax_request = null;
  this.last_preload_request = [];
  this.last_preload_request_active = false;

  debug.add_hook(this.get_debug.bind(this));

  this.image_loaded_event = this.image_loaded_event.bindAsEventListener(this);
  this.img = this.container.down(".image");
  this.img.observe("load", this.image_loaded_event);
}

BrowserView.prototype.image_loaded_event = function(event)
{
  document.fire("viewer:displayed-image-loaded", { post_id: this.displayed_post_id });
}

BrowserView.prototype.get_debug = function()
{
  var s = "wanted: " + this.wanted_post_id + ", displayed: " + this.displayed_post_id;
  if(this.lazy_load_timer)
    s += ", lazy load pending";
  return s;
}

/* Begin preloading the HTML and images for the given post IDs. */
BrowserView.prototype.preload = function(post_ids)
{
  /* We're being asked to preload post_ids.  Only do this if it seems to make sense: if
   * the user is actually traversing posts that are being preloaded.  Look at the previous
   * call to preload().  If it didn't include the current post, then skip the preload. */
  var last_preload_request = this.last_preload_request;
  this.last_preload_request = post_ids;
  if(last_preload_request.indexOf(this.wanted_post_id) == -1)
  {
    debug.log("skipped-preload(" + post_ids.join(",") + ")");
    this.last_preload_request_active = false;
    return;
  }
  this.last_preload_request_active = true;
  debug.log("preload(" + post_ids.join(",") + ")");

  var new_preload_container = new PreloadContainer();
  for(var i = 0; i < post_ids.length; ++i)
  {
    var post_id = post_ids[i];
    var post = Post.posts.get(post_id);
    new_preload_container.preload(post.sample_url);
  }

  /* If we already were preloading images, we created the new preloads before
   * deleting the old ones.  That way, any images that are still being preloaded
   * won't be deleted and recreated, possibly causing the download to be interrupted
   * and resumed. */
  if(this.preload_container)
    this.preload_container.destroy();
  this.preload_container = new_preload_container;
}

BrowserView.prototype.load_post_id_data = function(post_id)
{
  debug.log("load needed");

  // If we already have a request in flight, don't start another; wait for the
  // first to finish.
  if(this.current_ajax_request != null)
    return;

  new Ajax.Request("/post/index.json", {
    parameters: { tags: "id:" + post_id, filter: 1 },
    method: "get",

    onCreate: function(resp) {
      this.current_ajax_request = resp.request;
    }.bind(this),

    onComplete: function(resp) {
      if(this.current_ajax_request == resp.request)
        this.current_ajax_request = null;

      if(!resp.request.success() && post_id == this.wanted_post_id)
        return;

      /* This will either load the post we just finished, or request data for the
       * one we want. */
      this.set_post_content(this.wanted_post_id);
    }.bind(this),

    onSuccess: function(resp) {
      if(this.current_ajax_request != resp.request)
        return;

      var post = resp.responseJSON[0];
      Post.register(post);
    }.bind(this),

    onFailure: function(resp) {
      notice("Error " + resp.status + " loading post");
    }.bind(this)
  });
}

BrowserView.prototype.set_post_content = function(post_id)
{
  if(post_id == this.displayed_post_id)
    return;

  var post = Post.posts.get(post_id);
  if(post == null)
  {
    this.load_post_id_data(post_id);
    return;
  }

  this.displayed_post_id = post_id;
  UrlHash.set({"post-id": post_id});

  /* Clear the previous post, if any. */
  this.img.src = "about:blank";

  if(post)
  {
    this.img.hide();
    this.img.width = post.sample_width;
    this.img.height = post.sample_height;
    this.img.src = post.sample_url;
    this.img.show();

    Post.scale_and_fit_image(this.img);
  }

  Post.init_post_show(post_id);
// InitTextAreas();

  document.fire("viewer:displayed-post-changed", { post_id: post_id });
}

BrowserView.prototype.get_url_for_post_page = function(post_id)
{
  return "/post/show/" + post_id + "?browser=1";
}

BrowserView.prototype.set_post = function(post_id)
{
  /* If there was a lazy load pending, cancel it. */
  this.cancel_lazily_load();

  this.wanted_post_id = post_id;

  /* We don't have the node cached.  Open the page from HTML cache or start
   * loading the page as necessary. */
  this.set_post_content(post_id);
}

BrowserView.prototype.cancel_lazily_load = function()
{
  if(this.lazy_load_timer == null)
    return;

   window.clearTimeout(this.lazy_load_timer);
   this.lazy_load_timer = null;
}

BrowserView.prototype.lazily_load = function(post_id)
{
  this.cancel_lazily_load();

  /* If we already started the preload for the requested post, then use a small timeout. */
  var is_cached = this.last_preload_request_active && this.last_preload_request.indexOf(post_id) != -1;

  var ms = is_cached? 50:500;
  debug.log("post:" + post_id + ":" + is_cached + ":" + ms);

  /* Once lazily_load is called with a new post, we should consistently stay on the current
   * post or change to the new post.  We shouldn't change to a post that was previously
   * requested by lazily_load (due to a background request completing).  Mark whatever post
   * we're currently on as the one we want, until we're able to switch to the new one. */
  this.wanted_post_id = this.displayed_post_id;

  this.lazy_load_post_id = post_id;
  this.lazy_load_timer = window.setTimeout(function() {
    this.lazy_load_timer = null;
    this.set_post(post_id);
  }.bind(this), ms);
}

/* Update the window title when the display changes. */
WindowTitleHandler = function()
{
  this.searched_tags = "";
  this.post_id = null;
  this.pool = null;

  document.observe("viewer:searched-tags-changed", function(e) {
    this.searched_tags = e.memo.tags;
    this.update();
  }.bindAsEventListener(this));

  document.observe("viewer:displayed-post-changed", function(e) {
    this.post_id = e.memo.post_id;
    this.update();
  }.bindAsEventListener(this));

  document.observe("viewer:displayed-pool-changed", function(e) {
    this.pool = e.memo.pool;
    this.update();
  }.bindAsEventListener(this));

  this.update();
}

WindowTitleHandler.prototype.update = function()
{
  var post = Post.posts.get(this.post_id);

  if(this.pool)
  {
    var title = this.pool.name.replace(/_/g, " ");

    if(post && post.pool_post)
    {
      var sequence = post.pool_post.sequence;
      title += " ";
      if(sequence.match(/^[0-9]/))
        title += "#";
      title += sequence;
    }

    document.title = title;
    return;
  }

  var title = "/" + this.searched_tags.replace(/_/g, " ");
  document.title = title;
}

<commit_msg>tweak the title so browser tabs can be distinguished from post/index tabs<commit_after>/*
 * We have a few competing goals:
 *
 * First, be as responsive as possible.  Preload nearby post HTML and their images.
 *
 * If data in a post page changes, eg. if the user votes, then coming back to the page
 * later should retain the changes.  This means either requesting the page again, or
 * retaining the document node and reusing it, so we preserve the changes that were
 * made in-place.
 *
 * Don't use too much memory.  If we keep every document node in memory as we use it,
 * the images will probably be kept around too.  Release older nodes, so the browser
 * is more likely to release images that havn't been used in a while.
 *
 * We do the following:
 * - When we load a new post, it's formatted and its scripts are evaluated normally.
 * - When we're replacing the displayed post, its node is stashed away in a node cache.
 * - If we come back to the post while it's in the node cache, we'll use the node directly.
 * - HTML and images for posts are preloaded.  We don't use a simple mechanism like
 *   Preload.preload_raw, because Opera's caching is broken for XHR and it'll always
 *   do a slow revalidation.
 * - We don't depend on browser caching for HTML.  That would require us to expire a
 *   page when we switch away from it if we've made any changes (eg. voting), so we
 *   don't pull an out-of-date page next time.  This is slower, and would require us
 *   to be careful about expiring the cache.
 */

BrowserView = function(container)
{
  this.container = container;

  /* The post that we currently want to display.  This will be either one of the
   * current html_preloads, or be the displayed_post_id. */
  this.wanted_post_id = null;

  /* The post that's currently actually being displayed. */
  this.displayed_post_id = null;

  this.current_ajax_request = null;
  this.last_preload_request = [];
  this.last_preload_request_active = false;

  debug.add_hook(this.get_debug.bind(this));

  this.image_loaded_event = this.image_loaded_event.bindAsEventListener(this);
  this.img = this.container.down(".image");
  this.img.observe("load", this.image_loaded_event);
}

BrowserView.prototype.image_loaded_event = function(event)
{
  document.fire("viewer:displayed-image-loaded", { post_id: this.displayed_post_id });
}

BrowserView.prototype.get_debug = function()
{
  var s = "wanted: " + this.wanted_post_id + ", displayed: " + this.displayed_post_id;
  if(this.lazy_load_timer)
    s += ", lazy load pending";
  return s;
}

/* Begin preloading the HTML and images for the given post IDs. */
BrowserView.prototype.preload = function(post_ids)
{
  /* We're being asked to preload post_ids.  Only do this if it seems to make sense: if
   * the user is actually traversing posts that are being preloaded.  Look at the previous
   * call to preload().  If it didn't include the current post, then skip the preload. */
  var last_preload_request = this.last_preload_request;
  this.last_preload_request = post_ids;
  if(last_preload_request.indexOf(this.wanted_post_id) == -1)
  {
    debug.log("skipped-preload(" + post_ids.join(",") + ")");
    this.last_preload_request_active = false;
    return;
  }
  this.last_preload_request_active = true;
  debug.log("preload(" + post_ids.join(",") + ")");

  var new_preload_container = new PreloadContainer();
  for(var i = 0; i < post_ids.length; ++i)
  {
    var post_id = post_ids[i];
    var post = Post.posts.get(post_id);
    new_preload_container.preload(post.sample_url);
  }

  /* If we already were preloading images, we created the new preloads before
   * deleting the old ones.  That way, any images that are still being preloaded
   * won't be deleted and recreated, possibly causing the download to be interrupted
   * and resumed. */
  if(this.preload_container)
    this.preload_container.destroy();
  this.preload_container = new_preload_container;
}

BrowserView.prototype.load_post_id_data = function(post_id)
{
  debug.log("load needed");

  // If we already have a request in flight, don't start another; wait for the
  // first to finish.
  if(this.current_ajax_request != null)
    return;

  new Ajax.Request("/post/index.json", {
    parameters: { tags: "id:" + post_id, filter: 1 },
    method: "get",

    onCreate: function(resp) {
      this.current_ajax_request = resp.request;
    }.bind(this),

    onComplete: function(resp) {
      if(this.current_ajax_request == resp.request)
        this.current_ajax_request = null;

      if(!resp.request.success() && post_id == this.wanted_post_id)
        return;

      /* This will either load the post we just finished, or request data for the
       * one we want. */
      this.set_post_content(this.wanted_post_id);
    }.bind(this),

    onSuccess: function(resp) {
      if(this.current_ajax_request != resp.request)
        return;

      var post = resp.responseJSON[0];
      Post.register(post);
    }.bind(this),

    onFailure: function(resp) {
      notice("Error " + resp.status + " loading post");
    }.bind(this)
  });
}

BrowserView.prototype.set_post_content = function(post_id)
{
  if(post_id == this.displayed_post_id)
    return;

  var post = Post.posts.get(post_id);
  if(post == null)
  {
    this.load_post_id_data(post_id);
    return;
  }

  this.displayed_post_id = post_id;
  UrlHash.set({"post-id": post_id});

  /* Clear the previous post, if any. */
  this.img.src = "about:blank";

  if(post)
  {
    this.img.hide();
    this.img.width = post.sample_width;
    this.img.height = post.sample_height;
    this.img.src = post.sample_url;
    this.img.show();

    Post.scale_and_fit_image(this.img);
  }

  Post.init_post_show(post_id);
// InitTextAreas();

  document.fire("viewer:displayed-post-changed", { post_id: post_id });
}

BrowserView.prototype.get_url_for_post_page = function(post_id)
{
  return "/post/show/" + post_id + "?browser=1";
}

BrowserView.prototype.set_post = function(post_id)
{
  /* If there was a lazy load pending, cancel it. */
  this.cancel_lazily_load();

  this.wanted_post_id = post_id;

  /* We don't have the node cached.  Open the page from HTML cache or start
   * loading the page as necessary. */
  this.set_post_content(post_id);
}

BrowserView.prototype.cancel_lazily_load = function()
{
  if(this.lazy_load_timer == null)
    return;

   window.clearTimeout(this.lazy_load_timer);
   this.lazy_load_timer = null;
}

BrowserView.prototype.lazily_load = function(post_id)
{
  this.cancel_lazily_load();

  /* If we already started the preload for the requested post, then use a small timeout. */
  var is_cached = this.last_preload_request_active && this.last_preload_request.indexOf(post_id) != -1;

  var ms = is_cached? 50:500;
  debug.log("post:" + post_id + ":" + is_cached + ":" + ms);

  /* Once lazily_load is called with a new post, we should consistently stay on the current
   * post or change to the new post.  We shouldn't change to a post that was previously
   * requested by lazily_load (due to a background request completing).  Mark whatever post
   * we're currently on as the one we want, until we're able to switch to the new one. */
  this.wanted_post_id = this.displayed_post_id;

  this.lazy_load_post_id = post_id;
  this.lazy_load_timer = window.setTimeout(function() {
    this.lazy_load_timer = null;
    this.set_post(post_id);
  }.bind(this), ms);
}

/* Update the window title when the display changes. */
WindowTitleHandler = function()
{
  this.searched_tags = "";
  this.post_id = null;
  this.pool = null;

  document.observe("viewer:searched-tags-changed", function(e) {
    this.searched_tags = e.memo.tags;
    this.update();
  }.bindAsEventListener(this));

  document.observe("viewer:displayed-post-changed", function(e) {
    this.post_id = e.memo.post_id;
    this.update();
  }.bindAsEventListener(this));

  document.observe("viewer:displayed-pool-changed", function(e) {
    this.pool = e.memo.pool;
    this.update();
  }.bindAsEventListener(this));

  this.update();
}

WindowTitleHandler.prototype.update = function()
{
  var post = Post.posts.get(this.post_id);

  if(this.pool)
  {
    var title = "Browse " + this.pool.name.replace(/_/g, " ");

    if(post && post.pool_post)
    {
      var sequence = post.pool_post.sequence;
      title += " ";
      if(sequence.match(/^[0-9]/))
        title += "#";
      title += sequence;
    }

    document.title = title;
    return;
  }

  var title = "Browse /" + this.searched_tags.replace(/_/g, " ");
  document.title = title;
}

<|endoftext|>