<commit_before>/**
 * @license
 * Copyright 2021 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview A plugin that highlights the content on the workspace.
 */

import * as Blockly from 'blockly';

/**
 * List of events that cause a change in content area size.
 * @const
 * @private
 */
const CONTENT_CHANGE_EVENTS_ = [
  Blockly.Events.VIEWPORT_CHANGE,
  Blockly.Events.BLOCK_MOVE,
  Blockly.Events.BLOCK_DELETE,
];

/**
 * The default padding to use for the content highlight in workspace units.
 * @type {number}
 * @private
 */
const DEFAULT_PADDING_ = 10;

/**
 * Length of opacity change transition in seconds.
 * @type {number}
 * @const
 */
const ANIMATION_TIME = 0.25;

/**
 * A plugin that highlights the area where content exists on the workspace.
 */
export class ContentHighlight {
  /**
   * Constructor for the content highlight plugin.
   * @param {!Blockly.WorkspaceSvg} workspace The workspace that the plugin will
   *     be added to.
   */
  constructor(workspace) {
    /**
     * The workspace.
     * @type {!Blockly.WorkspaceSvg}
     * @protected
     */
    this.workspace_ = workspace;

    /**
     * The width of the highlight rectangle in workspace units.
     * @type {number}
     * @private
     */
    this.width_ = 0;

    /**
     * The height of the highlight rectangle in workspace units.
     * @type {number}
     * @private
     */
    this.height_ = 0;

    /**
     * The top offset of the highlight rectangle in pixels.
     * @type {number}
     * @private
     */
    this.top_ = 0;

    /**
     * The left offset of the highlight rectangle in pixels.
     * @type {number}
     * @private
     */
    this.left_ = 0;

    /**
     * The last scale value applied on the content highlight.
     * @type {number}
     * @private
     */
    this.lastScale_ = 1;

    /**
     * The cached content metrics for the workspace in workspace units.
     * @type {!Blockly.MetricsManager.ContainerRegion|undefined}
     * @private
     */
    this.cachedContentMetrics_ = undefined;
  }

  /**
   * Initializes the plugin.
   * @param {number=} padding The padding to use for the content area highlight
   *    rectangle, in workspace units.
   */
  init(padding) {
    padding = Number(padding);
    /**
     * The padding to use around the content area.
     * @const {number}
     */
    this.padding_ = isNaN(padding) ? DEFAULT_PADDING_ : padding;

    /** @type {SVGElement} */
    this.svgGroup_ = Blockly.utils.dom.createSvgElement(
        Blockly.utils.Svg.G,
        {'class': 'contentAreaHighlight'}, null);

    const rnd = String(Math.random()).substring(2);
    const mask = Blockly.utils.dom.createSvgElement(
        new Blockly.utils.Svg('mask'), {
          'id': 'contentAreaHighlightMask' + rnd,
        }, this.svgGroup_);
    Blockly.utils.dom.createSvgElement(
        Blockly.utils.Svg.RECT, {
          'x': 0,
          'y': 0,
          'width': '100%',
          'height': '100%',
          'fill': 'white',
        }, mask);
    this.rect_ = Blockly.utils.dom.createSvgElement(
        Blockly.utils.Svg.RECT, {
          'x': 0,
          'y': 0,
          'rx': Blockly.Bubble.BORDER_WIDTH,
          'ry': Blockly.Bubble.BORDER_WIDTH,
          'fill': 'black',
        }, mask);
    this.background_ = Blockly.utils.dom.createSvgElement(
        Blockly.utils.Svg.RECT, {
          'x': 0,
          'y': 0,
          'width': '100%',
          'height': '100%',
          'mask': 'url(#contentAreaHighlightMask' + rnd + ')',
        }, this.svgGroup_);

    this.applyColor_();
    const metricsManager = this.workspace_.getMetricsManager();
    this.cachedContentMetrics_ = metricsManager.getContentMetrics(true);
    this.resize_(this.cachedContentMetrics_);
    const absoluteMetrics = metricsManager.getAbsoluteMetrics();
    this.position_(this.cachedContentMetrics_, absoluteMetrics);

    // Apply transition animation for opacity changes.
    this.svgGroup_.style.transition = 'opacity ' + ANIMATION_TIME + 's';

    const parentSvg = this.workspace_.getParentSvg();
    if (parentSvg.firstChild) {
      parentSvg.insertBefore(this.svgGroup_, parentSvg.firstChild);
    } else {
      parentSvg.appendChild(this.svgGroup_);
    }

    this.onChangeWrapper_ = this.onChange_.bind(this);
    this.workspace_.addChangeListener(this.onChangeWrapper_);
  }

  /**
   * Disposes of content highlight.
   * Unlinks from all DOM elements and remove all event listeners
   * to prevent memory leaks.
   */
  dispose() {
    if (this.svgGroup_) {
      Blockly.utils.dom.removeNode(this.svgGroup_);
    }
    if (this.onChangeWrapper_) {
      Blockly.unbindEvent_(this.onChangeWrapper_);
    }
  }

  /**
   * Handles events triggered on the workspace.
   * @param {!Blockly.Events.Abstract} event The event.
   * @private
   */
  onChange_(event) {
    if (event.type === Blockly.Events.THEME_CHANGE) {
      this.applyColor_();
    } else if (CONTENT_CHANGE_EVENTS_.indexOf(event.type) !== -1) {
      const metricsManager = this.workspace_.getMetricsManager();
      if (event.type !== Blockly.Events.VIEWPORT_CHANGE) {
        // The content metrics change when it's not a viewport change event.
        this.cachedContentMetrics_ = metricsManager.getContentMetrics(true);
        this.resize_(this.cachedContentMetrics_);
      }
      const absoluteMetrics = metricsManager.getAbsoluteMetrics();
      this.position_(this.cachedContentMetrics_, absoluteMetrics);
    } else if (event.type === Blockly.Events.BLOCK_DRAG) {
      this.handleBlockDrag_(/** @type {!Blockly.Events.BlockDrag} */ event);
    }
  }

  /**
   * Changes opacity of the highlight based on what kind of block drag event
   * is passed.
   * @param {!Blockly.Events.BlockDrag} event The BlockDrag event.
   * @private
   */
  handleBlockDrag_(event) {
    const opacity = event.isStart ? '0' : '1';
    this.svgGroup_.setAttribute('opacity', opacity);
  }

  /**
   * Applies the color fill for the highlight based on the current theme.
   * @private
   */
  applyColor_() {
    const theme = this.workspace_.getTheme();
    const bgColor =
        theme.getComponentStyle('workspaceBackgroundColour') || '#ffffff';

    const colorDarkened =
        Blockly.utils.colour.blend('#000', bgColor, 0.1);
    const colorLightened =
        Blockly.utils.colour.blend('#fff', bgColor, 0.1);
    const color = (bgColor === '#ffffff' || bgColor === '#fff') ?
        colorDarkened : colorLightened;
    this.background_.setAttribute('fill', color);
  }

  /**
   * Resizes the content highlight.
   * @param {!Blockly.MetricsManager.ContainerRegion} contentMetrics The content
   *    metrics for the workspace in workspace coordinates.
   * @private
   */
  resize_(contentMetrics) {
    const width = contentMetrics.width ? contentMetrics.width +
        2 * this.padding_ : 0;
    const height = contentMetrics.height ? contentMetrics.height +
        2 * this.padding_ : 0;
    if (width !== this.width_) {
      this.width_ = width;
      this.rect_.setAttribute('width', width);
    }
    if (height !== this.height_) {
      this.height_ = height;
      this.rect_.setAttribute('height', height);
    }
  }

  /**
   * Positions the highlight on the workspace based on the workspace metrics.
   * @param {!Blockly.MetricsManager.ContainerRegion} contentMetrics The content
   *    metrics for the workspace in workspace coordinates.
   * @param {!Blockly.MetricsManager.AbsoluteMetrics} absoluteMetrics The
   *    absolute metrics for the workspace.
   * @private
   */
  position_(contentMetrics, absoluteMetrics) {
    // Compute top/left manually to avoid unnecessary extra computation.
    const viewTop = -this.workspace_.scrollY;
    const viewLeft = -this.workspace_.scrollX;
    const scale = this.workspace_.scale;
    const top = absoluteMetrics.top + contentMetrics.top * scale - viewTop -
        this.padding_ * scale;
    const left = absoluteMetrics.left + contentMetrics.left * scale - viewLeft -
        this.padding_ * scale;

    if (top !== this.top_ || left !== this.left_ ||
        this.lastScale_ !== scale) {
      this.top_ = top;
      this.left_ = left;
      this.lastScale_ = scale;
      this.rect_.setAttribute(
          'transform',
          'translate(' + left + ',' + top + ') scale(' + scale +')');
    }
  }
}
<commit_msg>fix: unbind the change listener correctly in content-highlight dispose (#1101)<commit_after>/**
 * @license
 * Copyright 2021 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview A plugin that highlights the content on the workspace.
 */

import * as Blockly from 'blockly';

/**
 * List of events that cause a change in content area size.
 * @const
 * @private
 */
const CONTENT_CHANGE_EVENTS_ = [
  Blockly.Events.VIEWPORT_CHANGE,
  Blockly.Events.BLOCK_MOVE,
  Blockly.Events.BLOCK_DELETE,
];

/**
 * The default padding to use for the content highlight in workspace units.
 * @type {number}
 * @private
 */
const DEFAULT_PADDING_ = 10;

/**
 * Length of opacity change transition in seconds.
 * @type {number}
 * @const
 */
const ANIMATION_TIME = 0.25;

/**
 * A plugin that highlights the area where content exists on the workspace.
 */
export class ContentHighlight {
  /**
   * Constructor for the content highlight plugin.
   * @param {!Blockly.WorkspaceSvg} workspace The workspace that the plugin will
   *     be added to.
   */
  constructor(workspace) {
    /**
     * The workspace.
     * @type {!Blockly.WorkspaceSvg}
     * @protected
     */
    this.workspace_ = workspace;

    /**
     * The width of the highlight rectangle in workspace units.
     * @type {number}
     * @private
     */
    this.width_ = 0;

    /**
     * The height of the highlight rectangle in workspace units.
     * @type {number}
     * @private
     */
    this.height_ = 0;

    /**
     * The top offset of the highlight rectangle in pixels.
     * @type {number}
     * @private
     */
    this.top_ = 0;

    /**
     * The left offset of the highlight rectangle in pixels.
     * @type {number}
     * @private
     */
    this.left_ = 0;

    /**
     * The last scale value applied on the content highlight.
     * @type {number}
     * @private
     */
    this.lastScale_ = 1;

    /**
     * The cached content metrics for the workspace in workspace units.
     * @type {!Blockly.MetricsManager.ContainerRegion|undefined}
     * @private
     */
    this.cachedContentMetrics_ = undefined;
  }

  /**
   * Initializes the plugin.
   * @param {number=} padding The padding to use for the content area highlight
   *    rectangle, in workspace units.
   */
  init(padding) {
    padding = Number(padding);
    /**
     * The padding to use around the content area.
     * @const {number}
     */
    this.padding_ = isNaN(padding) ? DEFAULT_PADDING_ : padding;

    /** @type {SVGElement} */
    this.svgGroup_ = Blockly.utils.dom.createSvgElement(
        Blockly.utils.Svg.G,
        {'class': 'contentAreaHighlight'}, null);

    const rnd = String(Math.random()).substring(2);
    const mask = Blockly.utils.dom.createSvgElement(
        new Blockly.utils.Svg('mask'), {
          'id': 'contentAreaHighlightMask' + rnd,
        }, this.svgGroup_);
    Blockly.utils.dom.createSvgElement(
        Blockly.utils.Svg.RECT, {
          'x': 0,
          'y': 0,
          'width': '100%',
          'height': '100%',
          'fill': 'white',
        }, mask);
    this.rect_ = Blockly.utils.dom.createSvgElement(
        Blockly.utils.Svg.RECT, {
          'x': 0,
          'y': 0,
          'rx': Blockly.Bubble.BORDER_WIDTH,
          'ry': Blockly.Bubble.BORDER_WIDTH,
          'fill': 'black',
        }, mask);
    this.background_ = Blockly.utils.dom.createSvgElement(
        Blockly.utils.Svg.RECT, {
          'x': 0,
          'y': 0,
          'width': '100%',
          'height': '100%',
          'mask': 'url(#contentAreaHighlightMask' + rnd + ')',
        }, this.svgGroup_);

    this.applyColor_();
    const metricsManager = this.workspace_.getMetricsManager();
    this.cachedContentMetrics_ = metricsManager.getContentMetrics(true);
    this.resize_(this.cachedContentMetrics_);
    const absoluteMetrics = metricsManager.getAbsoluteMetrics();
    this.position_(this.cachedContentMetrics_, absoluteMetrics);

    // Apply transition animation for opacity changes.
    this.svgGroup_.style.transition = 'opacity ' + ANIMATION_TIME + 's';

    const parentSvg = this.workspace_.getParentSvg();
    if (parentSvg.firstChild) {
      parentSvg.insertBefore(this.svgGroup_, parentSvg.firstChild);
    } else {
      parentSvg.appendChild(this.svgGroup_);
    }

    this.onChangeWrapper_ = this.onChange_.bind(this);
    this.workspace_.addChangeListener(this.onChangeWrapper_);
  }

  /**
   * Disposes of content highlight.
   * Unlinks from all DOM elements and remove all event listeners
   * to prevent memory leaks.
   */
  dispose() {
    if (this.svgGroup_) {
      Blockly.utils.dom.removeNode(this.svgGroup_);
    }
    if (this.onChangeWrapper_) {
      this.workspace_.removeChangeListener(this.onChangeWrapper_);
    }
  }

  /**
   * Handles events triggered on the workspace.
   * @param {!Blockly.Events.Abstract} event The event.
   * @private
   */
  onChange_(event) {
    if (event.type === Blockly.Events.THEME_CHANGE) {
      this.applyColor_();
    } else if (CONTENT_CHANGE_EVENTS_.indexOf(event.type) !== -1) {
      const metricsManager = this.workspace_.getMetricsManager();
      if (event.type !== Blockly.Events.VIEWPORT_CHANGE) {
        // The content metrics change when it's not a viewport change event.
        this.cachedContentMetrics_ = metricsManager.getContentMetrics(true);
        this.resize_(this.cachedContentMetrics_);
      }
      const absoluteMetrics = metricsManager.getAbsoluteMetrics();
      this.position_(this.cachedContentMetrics_, absoluteMetrics);
    } else if (event.type === Blockly.Events.BLOCK_DRAG) {
      this.handleBlockDrag_(/** @type {!Blockly.Events.BlockDrag} */ event);
    }
  }

  /**
   * Changes opacity of the highlight based on what kind of block drag event
   * is passed.
   * @param {!Blockly.Events.BlockDrag} event The BlockDrag event.
   * @private
   */
  handleBlockDrag_(event) {
    const opacity = event.isStart ? '0' : '1';
    this.svgGroup_.setAttribute('opacity', opacity);
  }

  /**
   * Applies the color fill for the highlight based on the current theme.
   * @private
   */
  applyColor_() {
    const theme = this.workspace_.getTheme();
    const bgColor =
        theme.getComponentStyle('workspaceBackgroundColour') || '#ffffff';

    const colorDarkened =
        Blockly.utils.colour.blend('#000', bgColor, 0.1);
    const colorLightened =
        Blockly.utils.colour.blend('#fff', bgColor, 0.1);
    const color = (bgColor === '#ffffff' || bgColor === '#fff') ?
        colorDarkened : colorLightened;
    this.background_.setAttribute('fill', color);
  }

  /**
   * Resizes the content highlight.
   * @param {!Blockly.MetricsManager.ContainerRegion} contentMetrics The content
   *    metrics for the workspace in workspace coordinates.
   * @private
   */
  resize_(contentMetrics) {
    const width = contentMetrics.width ? contentMetrics.width +
        2 * this.padding_ : 0;
    const height = contentMetrics.height ? contentMetrics.height +
        2 * this.padding_ : 0;
    if (width !== this.width_) {
      this.width_ = width;
      this.rect_.setAttribute('width', width);
    }
    if (height !== this.height_) {
      this.height_ = height;
      this.rect_.setAttribute('height', height);
    }
  }

  /**
   * Positions the highlight on the workspace based on the workspace metrics.
   * @param {!Blockly.MetricsManager.ContainerRegion} contentMetrics The content
   *    metrics for the workspace in workspace coordinates.
   * @param {!Blockly.MetricsManager.AbsoluteMetrics} absoluteMetrics The
   *    absolute metrics for the workspace.
   * @private
   */
  position_(contentMetrics, absoluteMetrics) {
    // Compute top/left manually to avoid unnecessary extra computation.
    const viewTop = -this.workspace_.scrollY;
    const viewLeft = -this.workspace_.scrollX;
    const scale = this.workspace_.scale;
    const top = absoluteMetrics.top + contentMetrics.top * scale - viewTop -
        this.padding_ * scale;
    const left = absoluteMetrics.left + contentMetrics.left * scale - viewLeft -
        this.padding_ * scale;

    if (top !== this.top_ || left !== this.left_ ||
        this.lastScale_ !== scale) {
      this.top_ = top;
      this.left_ = left;
      this.lastScale_ = scale;
      this.rect_.setAttribute(
          'transform',
          'translate(' + left + ',' + top + ') scale(' + scale +')');
    }
  }
}
<|endoftext|>


<commit_before>var show = (function(){
  var move = function(e) {
    var DIRECTIONS = {
      37: -1,     // >
      38: -1,     // up
      39: 1,      // <
      40: 1,      // down
      32: 1,      // _
      13: 1,      // return
      27: 'home', // esc
      left: -1,
      right: 1
    };

    if (dir = DIRECTIONS[e.which || e]) {
      if (dir == 'home') {
        e.preventDefault();
        e.stopPropagation();
        location.href = '/';
      } else {
        show.setIndex(show.index() + dir);
      }
    }
  };

  var clickMove = function(e) {
    if (e.pageX < ($(window).width() / 2)) {
      move('left');
    } else {
      move('right');
    }
  }

  var dimensions = function() {
    return {
      width: $(window).width(),
      height: $(window).height()
    };
  };

  var setSlideDimensions = function() {
    var d = dimensions();
    $('#slides').height(d.height).width(d.width);
    show.slides().height(d.height).width(d.width);
  };

  var showCurrentSlide = function() {
    var d = dimensions();
    var index = (show.index() || 0);
    var offset = index * $('#slides').width();
    $('#reel').animate({ marginLeft: '-' + offset + 'px' }, 200);
  };

  var verticalAlign = function() {
    var d = dimensions();
    var margin = (dimensions.height - $(this).height()) / 2;
    $(this).css({ paddingTop: margin + 'px' });
  };

  var adjustSlides = function() {
    setSlideDimensions();
    showCurrentSlide();
  };

  var followLinks = function(e) {
    e.stopPropagation();
    e.preventDefault();
    window.open(e.target.href);
  }

   $(window).bind('resize', function() { adjustSlides(); });

   $(document).bind('keydown', move);

   $(document).bind("click", clickMove);

   return {
      slides: function() {
        return $('#slides .content');
      },
      index: function() {
        return Number(document.location.hash.split('#')[1]);
      },
      setIndex: function(i) {
        var newSlide = '#slide-' + i;
        if ($(newSlide).size() < 1) {
          return false;
        } else {
          document.location.hash = '#' + i;
          adjustSlides();
          $("a").unbind("click").click(followLinks);
        }
      },
      go: function() {
        this.setIndex(this.index() || 0);
      }
    };
  })();

 $(document).ready(function() {
    show.go();
  });
<commit_msg>add touch event handler<commit_after>var show = (function(){
  var move = function(e) {
    var DIRECTIONS = {
      37: -1,     // >
      38: -1,     // up
      39: 1,      // <
      40: 1,      // down
      32: 1,      // _
      13: 1,      // return
      27: 'home', // esc
      left: -1,
      right: 1
    };

    if (dir = DIRECTIONS[e.which || e]) {
      if (dir == 'home') {
        e.preventDefault();
        e.stopPropagation();
        location.href = '/';
      } else {
        show.setIndex(show.index() + dir);
      }
    }
  };

  var clickMove = function(e) {
    if (e.pageX < ($(window).width() / 2)) {
      move('left');
    } else {
      move('right');
    }
  }

  var touchMove = function(e) {
    if (e.originalEvent.changedTouches[0].pageX < ($(window).width() / 2)) {
      move('left');
    } else {
      move('right');
    }
  }

  var dimensions = function() {
    return {
      width: $(window).width(),
      height: $(window).height()
    };
  };

  var setSlideDimensions = function() {
    var d = dimensions();
    $('#slides').height(d.height).width(d.width);
    show.slides().height(d.height).width(d.width);
  };

  var showCurrentSlide = function() {
    var d = dimensions();
    var index = (show.index() || 0);
    var offset = index * $('#slides').width();
    $('#reel').animate({ marginLeft: '-' + offset + 'px' }, 200);
  };

  var verticalAlign = function() {
    var d = dimensions();
    var margin = (dimensions.height - $(this).height()) / 2;
    $(this).css({ paddingTop: margin + 'px' });
  };

  var adjustSlides = function() {
    setSlideDimensions();
    showCurrentSlide();
  };

  var followLinks = function(e) {
    e.stopPropagation();
    e.preventDefault();
    window.open(e.target.href);
  }

   $(window).bind('resize', function() { adjustSlides(); });

   $(document).bind('keydown', move);

   $(document).bind("click", clickMove);

   $(document).bind("touchend", touchMove);

   return {
      slides: function() {
        return $('#slides .content');
      },
      index: function() {
        return Number(document.location.hash.split('#')[1]);
      },
      setIndex: function(i) {
        var newSlide = '#slide-' + i;
        if ($(newSlide).size() < 1) {
          return false;
        } else {
          document.location.hash = '#' + i;
          adjustSlides();
          $("a").unbind("click").click(followLinks);
        }
      },
      go: function() {
        this.setIndex(this.index() || 0);
      }
    };
  })();

 $(document).ready(function() {
    show.go();
  });
<|endoftext|>


<commit_before>// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var scrollTopButton = document.getElementById("to-top-button");

window.onscroll = function() {
  scrollFunction()
};

function scrollFunction() {
  if (document.body.scrollTop > 10 || document.documentElement.scrollTop > 10) {
    scrollTopButton.style.display = "block";
  } else {
    scrollTopButton.style.display = "none";
  }
}

// When the user clicks on the button, scroll to the top of the document
function toTopFunction() {
  document.body.scrollTop = 0;
  document.documentElement.scrollTop = 0;
}
<commit_msg>CL fix<commit_after>// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const scrollTopButton = document.getElementById("to-top-button");

window.onscroll = function() {
  scrollFunction()
};

function scrollFunction() {
  if (document.body.scrollTop > 10 || document.documentElement.scrollTop > 10) {
    scrollTopButton.style.display = "block";
  } else {
    scrollTopButton.style.display = "none";
  }
}

// When the user clicks on the button, scroll to the top of the document
function toTopFunction() {
  document.body.scrollTop = 0;
  document.documentElement.scrollTop = 0;
}
<|endoftext|>


<commit_before>L.Map.mergeOptions({
	zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android && !L.Browser.mobileOpera
});

L.Map.include(!L.DomUtil.TRANSITION ? {} : {
	_zoomToIfCenterInView: function (center, zoom, centerOffset) {

		if (this._animatingZoom) {
			return true;
		}
		if (!this.options.zoomAnimation) {
			return false;
		}

		var scale = Math.pow(2, zoom - this._zoom),
			offset = centerOffset.divideBy(1 - 1 / scale);

		// if offset does not exceed half of the view
		if (!this._offsetIsWithinView(offset, 1)) {
			return false;
		}

		this._mapPane.className += ' leaflet-zoom-anim';

		this
			.fire('movestart')
			.fire('zoomstart');

		//Hack: Disable this for android due to it not supporting double translate (mentioned in _runAnimation below)
		//if Foreground layer doesn't have many tiles but bg layer does, keep the existing bg layer
		if (!L.Browser.android && this._tileBg && this._getLoadedTilesPercentage(this._tileBg) > 0.5 && this._getLoadedTilesPercentage(this._tilePane) < 0.5) {
			//Leave current bg and just zoom it some more

			this._tilePane.style.visibility = 'hidden';
			this._tilePane.empty = true;
			this._stopLoadingImages(this._tilePane);
		} else {
			this._prepareTileBg();
		}

		var centerPoint = this.containerPointToLayerPoint(this.getSize().divideBy(2)),
			origin = centerPoint.add(offset);

		var zoomBackup = this._zoom;
		this._zoom = zoom;

		var tlBackup = this._initialTopLeftPoint;
		this._initialTopLeftPoint = this._getNewTopLeftPoint(center).add(L.DomUtil.getPosition(this._mapPane)); // this._getNewTopLeftPoint(center);

		for (var i in this._layers) {
			if ((!this._layers[i]._icon && !this._layers[i]._container) || !this._layers[i]._latlng || this._layers[i]._path)
			//if ((!this._layers[i]._icon && !this._layers[i]._container) || !this._layers[i]._latlng)
				continue;

			var box = this._layers[i]._icon || this._layers[i]._container;
			var np = this.latLngToLayerPoint(this._layers[i]._latlng);

			//L.Util.falseFn(box.offsetWidth);

			box.style[L.Transition.DURATION] = '0.25s';
			box.style[L.Transition.EASING] = 'cubic-bezier(0.25,0.1,0.25,0.75)';
			box.style[L.Transition.PROPERTY] = this._tileBg.transition._dasherize(L.DomUtil.TRANSFORM);

			box.style[L.DomUtil.TRANSFORM] = 'translate3d(' + np.x + 'px,' + np.y + 'px,0)';

			box = this._layers[i]._shadow;
			if (box) {
				box.style[L.Transition.DURATION] = '0.25s';
				box.style[L.Transition.EASING] = 'cubic-bezier(0.25,0.1,0.25,0.75)';
				box.style[L.Transition.PROPERTY] = this._tileBg.transition._dasherize(L.DomUtil.TRANSFORM);

				box.style[L.DomUtil.TRANSFORM] = 'translate3d(' + np.x + 'px,' + np.y + 'px,0)';
			}
		}
		this._zoom = zoomBackup;
		this._initialTopLeftPoint = tlBackup;

		this._runAnimation(center, zoom, scale, origin);

		return true;
	},

	_runAnimation: function (center, zoom, scale, origin, backwardsTransform) {
		this._animatingZoom = true;

		this._animateToCenter = center;
		this._animateToZoom = zoom;

		var transform = L.DomUtil.TRANSFORM,
			tileBg = this._tileBg;

		clearTimeout(this._clearTileBgTimer);

		//dumb FireFox hack, I have no idea why this magic zero translate fixes the scale transition problem
		if (L.Browser.gecko || window.opera) {
			tileBg.style[transform] += ' translate(0,0)';
		}

		var scaleStr;

		// Android 2.* doesn't like translate/scale chains, transformOrigin + scale works better but
		// it breaks touch zoom which Anroid doesn't support anyway, so that's a really ugly hack

		// TODO work around this prettier
		if (L.Browser.android) {
			tileBg.style[transform + 'Origin'] = origin.x + 'px ' + origin.y + 'px';
			scaleStr = 'scale(' + scale + ')';
		} else {
			scaleStr = L.DomUtil.getScaleString(scale, origin);
		}

		L.Util.falseFn(tileBg.offsetWidth); //hack to make sure transform is updated before running animation

		var options = {};
		if (backwardsTransform) {
			options[transform] = tileBg.style[transform] + ' ' + scaleStr;
		} else {
			options[transform] = scaleStr + ' ' + tileBg.style[transform];
		}

		origin.x -= this._pathRoot.offsetWidth / 2;
		origin.y -= this._pathRoot.offsetHeight / 2;
		this._pathRoot.style[L.Transition.DURATION] = '0.25s';
		this._pathRoot.style[L.Transition.EASING] = 'cubic-bezier(0.25,0.1,0.25,0.75)';
		this._pathRoot.style[L.Transition.PROPERTY] = '-webkit-transform';
		this._pathRoot.style[L.DomUtil.TRANSFORM] = L.DomUtil.getScaleString(scale, origin) + ' ' + this._pathRoot.style[L.DomUtil.TRANSFORM];

		tileBg.transition.run(options);
	},

	_prepareTileBg: function () {
		var tilePane = this._tilePane,
			tileBg = this._tileBg;

		if (!tileBg) {
			tileBg = this._tileBg = this._createPane('leaflet-tile-pane', this._mapPane);
			tileBg.style.zIndex = 1;
		}

		// prepare the background pane to become the main tile pane
		tileBg.style[L.DomUtil.TRANSFORM] = '';
		tileBg.style.visibility = 'hidden';

		// tells tile layers to reinitialize their containers
		tileBg.empty = true; //new FG
		tilePane.empty = false; //new BG

		//Switch out the current layer to be the new bg layer (And vice-versa)
		this._tilePane = this._panes.tilePane = tileBg;
		var newTileBg = this._tileBg = tilePane;

		if (!newTileBg.transition) {
			// TODO move to Map options
			newTileBg.transition = new L.Transition(newTileBg, {
				duration: 0.25,
				easing: 'cubic-bezier(0.25,0.1,0.25,0.75)'
			});
			newTileBg.transition.on('end', this._onZoomTransitionEnd, this);
		}

		this._stopLoadingImages(newTileBg);
	},

	_getLoadedTilesPercentage: function (container) {
		var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
			i, len, count = 0;

		for (i = 0, len = tiles.length; i < len; i++) {
			if (tiles[i].complete) {
				count++;
			}
		}
		return count / len;
	},

	// stops loading all tiles in the background layer
	_stopLoadingImages: function (container) {
		var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
			i, len, tile;

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

			if (!tile.complete) {
				tile.onload = L.Util.falseFn;
				tile.onerror = L.Util.falseFn;
				tile.src = L.Util.emptyImageUrl;

				tile.parentNode.removeChild(tile);
			}
		}
	},

	_onZoomTransitionEnd: function () {
		this._restoreTileFront();

		L.Util.falseFn(this._tileBg.offsetWidth);
		this._resetView(this._animateToCenter, this._animateToZoom, true, true);

		this._mapPane.className = this._mapPane.className.replace(' leaflet-zoom-anim', ''); //TODO toggleClass util
		this._animatingZoom = false;
	},

	_restoreTileFront: function () {
		this._tilePane.innerHTML = '';
		this._tilePane.style.visibility = '';
		this._tilePane.style.zIndex = 2;
		this._tileBg.style.zIndex = 1;
	},

	_clearTileBg: function () {
		if (!this._animatingZoom && !this.touchZoom._zooming) {
			this._tileBg.innerHTML = '';
		}
	}
});
<commit_msg>Fix up SVG zooming with firefox<commit_after>L.Map.mergeOptions({
	zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android && !L.Browser.mobileOpera
});

L.Map.include(!L.DomUtil.TRANSITION ? {} : {
	_zoomToIfCenterInView: function (center, zoom, centerOffset) {

		if (this._animatingZoom) {
			return true;
		}
		if (!this.options.zoomAnimation) {
			return false;
		}

		var scale = Math.pow(2, zoom - this._zoom),
			offset = centerOffset.divideBy(1 - 1 / scale);

		// if offset does not exceed half of the view
		if (!this._offsetIsWithinView(offset, 1)) {
			return false;
		}

		this._mapPane.className += ' leaflet-zoom-anim';

		this
			.fire('movestart')
			.fire('zoomstart');

		//Hack: Disable this for android due to it not supporting double translate (mentioned in _runAnimation below)
		//if Foreground layer doesn't have many tiles but bg layer does, keep the existing bg layer
		if (!L.Browser.android && this._tileBg && this._getLoadedTilesPercentage(this._tileBg) > 0.5 && this._getLoadedTilesPercentage(this._tilePane) < 0.5) {
			//Leave current bg and just zoom it some more

			this._tilePane.style.visibility = 'hidden';
			this._tilePane.empty = true;
			this._stopLoadingImages(this._tilePane);
		} else {
			this._prepareTileBg();
		}

		var centerPoint = this.containerPointToLayerPoint(this.getSize().divideBy(2)),
			origin = centerPoint.add(offset);

		var zoomBackup = this._zoom;
		this._zoom = zoom;

		var tlBackup = this._initialTopLeftPoint;
		this._initialTopLeftPoint = this._getNewTopLeftPoint(center).add(L.DomUtil.getPosition(this._mapPane)); // this._getNewTopLeftPoint(center);

		for (var i in this._layers) {
			if ((!this._layers[i]._icon && !this._layers[i]._container) || !this._layers[i]._latlng || this._layers[i]._path)
			//if ((!this._layers[i]._icon && !this._layers[i]._container) || !this._layers[i]._latlng)
				continue;

			var box = this._layers[i]._icon || this._layers[i]._container;
			var np = this.latLngToLayerPoint(this._layers[i]._latlng);

			//L.Util.falseFn(box.offsetWidth);

			box.style[L.Transition.DURATION] = '0.25s';
			box.style[L.Transition.EASING] = 'cubic-bezier(0.25,0.1,0.25,0.75)';
			box.style[L.Transition.PROPERTY] = this._tileBg.transition._dasherize(L.DomUtil.TRANSFORM);

			box.style[L.DomUtil.TRANSFORM] = 'translate3d(' + np.x + 'px,' + np.y + 'px,0)';

			box = this._layers[i]._shadow;
			if (box) {
				box.style[L.Transition.DURATION] = '0.25s';
				box.style[L.Transition.EASING] = 'cubic-bezier(0.25,0.1,0.25,0.75)';
				box.style[L.Transition.PROPERTY] = this._tileBg.transition._dasherize(L.DomUtil.TRANSFORM);

				box.style[L.DomUtil.TRANSFORM] = 'translate3d(' + np.x + 'px,' + np.y + 'px,0)';
			}
		}
		this._zoom = zoomBackup;
		this._initialTopLeftPoint = tlBackup;

		this._runAnimation(center, zoom, scale, origin);

		return true;
	},

	_runAnimation: function (center, zoom, scale, origin, backwardsTransform) {
		this._animatingZoom = true;

		this._animateToCenter = center;
		this._animateToZoom = zoom;

		var transform = L.DomUtil.TRANSFORM,
			tileBg = this._tileBg;

		clearTimeout(this._clearTileBgTimer);

		//dumb FireFox hack, I have no idea why this magic zero translate fixes the scale transition problem
		if (L.Browser.gecko || window.opera) {
			tileBg.style[transform] += ' translate(0,0)';
		}

		var scaleStr;

		// Android 2.* doesn't like translate/scale chains, transformOrigin + scale works better but
		// it breaks touch zoom which Anroid doesn't support anyway, so that's a really ugly hack

		// TODO work around this prettier
		if (L.Browser.android) {
			tileBg.style[transform + 'Origin'] = origin.x + 'px ' + origin.y + 'px';
			scaleStr = 'scale(' + scale + ')';
		} else {
			scaleStr = L.DomUtil.getScaleString(scale, origin);
		}

		L.Util.falseFn(tileBg.offsetWidth); //hack to make sure transform is updated before running animation

		var options = {};
		if (backwardsTransform) {
			options[transform] = tileBg.style[transform] + ' ' + scaleStr;
		} else {
			options[transform] = scaleStr + ' ' + tileBg.style[transform];
		}

		origin = origin.subtract(this._size);
		this._pathRoot.style[L.Transition.DURATION] = '0.25s';
		this._pathRoot.style[L.Transition.EASING] = 'cubic-bezier(0.25,0.1,0.25,0.75)';
		this._pathRoot.style[L.Transition.PROPERTY] = this._tileBg.transition._dasherize(L.DomUtil.TRANSFORM);
		this._pathRoot.style[L.DomUtil.TRANSFORM] = L.DomUtil.getScaleString(scale, origin) + ' ' + this._pathRoot.style[L.DomUtil.TRANSFORM];

		tileBg.transition.run(options);
	},

	_prepareTileBg: function () {
		var tilePane = this._tilePane,
			tileBg = this._tileBg;

		if (!tileBg) {
			tileBg = this._tileBg = this._createPane('leaflet-tile-pane', this._mapPane);
			tileBg.style.zIndex = 1;
		}

		// prepare the background pane to become the main tile pane
		tileBg.style[L.DomUtil.TRANSFORM] = '';
		tileBg.style.visibility = 'hidden';

		// tells tile layers to reinitialize their containers
		tileBg.empty = true; //new FG
		tilePane.empty = false; //new BG

		//Switch out the current layer to be the new bg layer (And vice-versa)
		this._tilePane = this._panes.tilePane = tileBg;
		var newTileBg = this._tileBg = tilePane;

		if (!newTileBg.transition) {
			// TODO move to Map options
			newTileBg.transition = new L.Transition(newTileBg, {
				duration: 0.25,
				easing: 'cubic-bezier(0.25,0.1,0.25,0.75)'
			});
			newTileBg.transition.on('end', this._onZoomTransitionEnd, this);
		}

		this._stopLoadingImages(newTileBg);
	},

	_getLoadedTilesPercentage: function (container) {
		var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
			i, len, count = 0;

		for (i = 0, len = tiles.length; i < len; i++) {
			if (tiles[i].complete) {
				count++;
			}
		}
		return count / len;
	},

	// stops loading all tiles in the background layer
	_stopLoadingImages: function (container) {
		var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
			i, len, tile;

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

			if (!tile.complete) {
				tile.onload = L.Util.falseFn;
				tile.onerror = L.Util.falseFn;
				tile.src = L.Util.emptyImageUrl;

				tile.parentNode.removeChild(tile);
			}
		}
	},

	_onZoomTransitionEnd: function () {
		this._restoreTileFront();

		L.Util.falseFn(this._tileBg.offsetWidth);
		this._resetView(this._animateToCenter, this._animateToZoom, true, true);

		this._mapPane.className = this._mapPane.className.replace(' leaflet-zoom-anim', ''); //TODO toggleClass util
		this._animatingZoom = false;
	},

	_restoreTileFront: function () {
		this._tilePane.innerHTML = '';
		this._tilePane.style.visibility = '';
		this._tilePane.style.zIndex = 2;
		this._tileBg.style.zIndex = 1;
	},

	_clearTileBg: function () {
		if (!this._animatingZoom && !this.touchZoom._zooming) {
			this._tileBg.innerHTML = '';
		}
	}
});
<|endoftext|>


<commit_before>import fluxlet from "./fluxlet";
import { deepFreeze } from "./utils";

import { extend } from "jquery";
const deepExtend = extend.bind(undefined, true);

var instance;
var gathered;

export var given = {

    // Create a fluxlet for testing against (id is optional)
    fluxlet(id) {
        instance = fluxlet(id)
            .logging({register: false, dispatch: true, call: false})
            // Register a side effect that gathers the state and end states that are passed
            // to the side effects after the action and calculation fns have been called in a dispatch.
            .sideEffects({
                gatherState: (state, prev) => {
                    gathered = { state, prev };
                }
            });

        return this;
    },

    // Set the initial state.
    // Generally called in a beforeEach with the default state, but may also be called in
    // individual test cases to override parts of the default state.
    state(overridingState) {
        instance.state(state => deepFreeze(deepExtend({}, state, overridingState)));
        const state = instance.debug.state();
        gathered = { state, prev: state };
        return this;
    },

    // Register actions for testing
    actions(namedActions) {
        instance.actions(namedActions);
        return this;
    },

    // Register calculations for testing
    calculations(namedCalculations) {
        instance.calculations(namedCalculations);
        return this;
    },

    // Register side effects for testing
    sideEffects(namedSideEffects) {
        instance.sideEffects(namedSideEffects);
        return this;
    },

    // Adjust logging levels
    logging(categories) {
        instance.logging(categories);
        return this;
    }
};

// Returns all dispatchers from the fluxlet for calling within the test, eg:
//     when().someAction("foo")
export function when() {
    return instance.debug.dispatchers();
}

// Call a fn passing the new and previous state, in which assertions can be performed about the expected state, eg:
//     then(state => {
//         expect(state.foo).toBe("bar");
//     });
export function then(fn) {
    fn(gathered.state, gathered.prev);
}

// Register a conditional side effect in which to perform assertions, useful for async tests using the
// done function passed in by jasmine, eg:
//     describe("something", (done) => {
//         ...
//         thenAfter((state, prev) => state.foo !== prev.foo,
//             state => {
//                 expect(state.foo).toBe("bar");
//                 done();
//             }
//         );
//     });
export function thenAfter(when, then) {
    instance.sideEffects({
        testExpectedStateChange: { when, then }
    });
}

export function noop(){
    return function(state) {
        return state;
    };
}
<commit_msg>Remove logging override in testlet<commit_after>import fluxlet from "./fluxlet";
import { deepFreeze } from "./utils";

import { extend } from "jquery";
const deepExtend = extend.bind(undefined, true);

var instance;
var gathered;

export var given = {

    // Create a fluxlet for testing against (id is optional)
    fluxlet(id) {
        instance = fluxlet(id)
            // Register a side effect that gathers the state and end states that are passed
            // to the side effects after the action and calculation fns have been called in a dispatch.
            .sideEffects({
                gatherState: (state, prev) => {
                    gathered = { state, prev };
                }
            });

        return this;
    },

    // Set the initial state.
    // Generally called in a beforeEach with the default state, but may also be called in
    // individual test cases to override parts of the default state.
    state(overridingState) {
        instance.state(state => deepFreeze(deepExtend({}, state, overridingState)));
        const state = instance.debug.state();
        gathered = { state, prev: state };
        return this;
    },

    // Register actions for testing
    actions(namedActions) {
        instance.actions(namedActions);
        return this;
    },

    // Register calculations for testing
    calculations(namedCalculations) {
        instance.calculations(namedCalculations);
        return this;
    },

    // Register side effects for testing
    sideEffects(namedSideEffects) {
        instance.sideEffects(namedSideEffects);
        return this;
    },

    // Adjust logging levels
    logging(categories) {
        instance.logging(categories);
        return this;
    }
};

// Returns all dispatchers from the fluxlet for calling within the test, eg:
//     when().someAction("foo")
export function when() {
    return instance.debug.dispatchers();
}

// Call a fn passing the new and previous state, in which assertions can be performed about the expected state, eg:
//     then(state => {
//         expect(state.foo).toBe("bar");
//     });
export function then(fn) {
    fn(gathered.state, gathered.prev);
}

// Register a conditional side effect in which to perform assertions, useful for async tests using the
// done function passed in by jasmine, eg:
//     describe("something", (done) => {
//         ...
//         thenAfter((state, prev) => state.foo !== prev.foo,
//             state => {
//                 expect(state.foo).toBe("bar");
//                 done();
//             }
//         );
//     });
export function thenAfter(when, then) {
    instance.sideEffects({
        testExpectedStateChange: { when, then }
    });
}

export function noop(){
    return function(state) {
        return state;
    };
}
<|endoftext|>


<commit_before>$(document).ready(function() {
  // HTML markup implementation, cover mode
  $('#menu').multilevelpushmenu({
    containersToPush: [$('pushobj')],
    mode: 'cover',
    onItemClick: function() {
      $item = arguments[2];
      var idOfClicked = $item[0].id;
      // If the item has the id 'deleteChapter', then spawn the modal
      if (idOfClicked === 'deleteChapter') {
        $('#chapterDelete').modal('show');
      }
    },
    onCollapseMenuEnd: function() {
      $('#pushobj').css('width', '94%');
    },
    onExpandMenuStart: function() {
      $('#pushobj').css('width', '74%');
    }
  });

  // Create chapter to add
  // TODO: Use actual number
  var addChapter = [
    {
      name: 'Chapter #',
      link: '#',
      items: [
        {
          title: 'Chapter #',
          icon: 'fa fa-bookmark',
          items: [
            {
              name: 'Chapter Info',
              icon: 'fa fa-info-circle',
              link: '#'
            },
            {
              name: 'StoryLayers',
              icon: 'fa fa-clone',
              link: '#'
            },
            {
              name: 'StoryBoxes',
              icon: 'fa fa-object-group',
              link: '#'
            },
            {
              name: 'StoryPins',
              icon: 'fa fa-neuter',
              link: '#'
            },
            {
              name: 'Delete Chapter',
              id: 'deleteChapter',
              icon: 'fa fa-trash-o',
              link: '#'
            }
          ]
        }
      ]
    }
  ];

  $('#addchapter').click(function() {
    var $addTo = $('#menu').multilevelpushmenu('activemenu').first();
    // Check what the 0 does
    $('#menu').multilevelpushmenu('additems', addChapter, $addTo, 0);
  });

  // Fully delete the chapter - wip
  $('#removeChapter').click(function() {
    console.log('Removing chapter');
    var item = $('#menu').multilevelpushmenu('finditemsbyname', 'Chapter #');
    $('#menu').multilevelpushmenu('removeitems', item);
  });
});
<commit_msg>Fix bug in map size<commit_after>$(document).ready(function() {
  // HTML markup implementation, cover mode
  $('#menu').multilevelpushmenu({
    containersToPush: [$('pushobj')],
    mode: 'cover',
    onItemClick: function() {
      $item = arguments[2];
      var idOfClicked = $item[0].id;
      // If the item has the id 'deleteChapter', then spawn the modal
      if (idOfClicked === 'deleteChapter') {
        $('#chapterDelete').modal('show');
      }
    },
    onCollapseMenuEnd: function() {
      // Only if the entire menu is deactivated, expand map
      var active = $('#menu').multilevelpushmenu('activemenu');
      console.log(active);
      if (active.prevObject.length === 0) {
        $('#pushobj').css('width', '94%');
      }
    },
    onExpandMenuStart: function() {
      $('#pushobj').css('width', '74%');
    }
  });

  // Create chapter to add
  // TODO: Use actual number
  var addChapter = [
    {
      name: 'Chapter #',
      link: '#',
      items: [
        {
          title: 'Chapter #',
          icon: 'fa fa-bookmark',
          items: [
            {
              name: 'Chapter Info',
              icon: 'fa fa-info-circle',
              link: '#'
            },
            {
              name: 'StoryLayers',
              icon: 'fa fa-clone',
              link: '#'
            },
            {
              name: 'StoryBoxes',
              icon: 'fa fa-object-group',
              link: '#'
            },
            {
              name: 'StoryPins',
              icon: 'fa fa-neuter',
              link: '#'
            },
            {
              name: 'Delete Chapter',
              id: 'deleteChapter',
              icon: 'fa fa-trash-o',
              link: '#'
            }
          ]
        }
      ]
    }
  ];

  $('#addchapter').click(function() {
    var $addTo = $('#menu').multilevelpushmenu('activemenu').first();
    // Check what the 0 does
    $('#menu').multilevelpushmenu('additems', addChapter, $addTo, 0);
  });

  // Fully delete the chapter - wip
  $('#removeChapter').click(function() {
    console.log('Removing chapter');
    var item = $('#menu').multilevelpushmenu('finditemsbyname', 'Chapter #');
    $('#menu').multilevelpushmenu('removeitems', item);
  });
});
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2016 hustcc
 * License: MIT
 * Version: %%GULP_INJECT_VERSION%%
 * https://github.com/hustcc/timeago.js
**/
/* jshint expr: true */
!function (root, factory) {
  if (typeof module === 'object' && module.exports) {
    module.exports = factory(root); // nodejs support
    module.exports['default'] = module.exports; // es6 support
  }
  else
    root.timeago = factory(root);
}(typeof window !== 'undefined' ? window : this,
function () {
  var indexMapEn = 'second_minute_hour_day_week_month_year'.split('_'),
    indexMapZh = '秒_分钟_小时_天_周_月_年'.split('_'),
    // build-in locales: en & zh_CN
    locales = {
      'en': function(number, index) {
        if (index === 0) return ['just now', 'right now'];
        var unit = indexMapEn[parseInt(index / 2)];
        if (number > 1) unit += 's';
        return [number + ' ' + unit + ' ago', 'in ' + number + ' ' + unit];
      },
      'zh_CN': function(number, index) {
        if (index === 0) return ['刚刚', '片刻后'];
        var unit = indexMapZh[parseInt(index / 2)];
        return [number + unit + '前', number + unit + '后'];
      }
    },
    // second, minute, hour, day, week, month, year(365 days)
    SEC_ARRAY = [60, 60, 24, 7, 365/7/12, 12],
    SEC_ARRAY_LEN = 6,
    ATTR_DATETIME = 'datetime',
    ATTR_DATA_TID = 'data-tid',
    timers = {}; // real-time render timers

  // format Date / string / timestamp to Date instance.
  function toDate(input) {
    if (input instanceof Date) return input;
    if (!isNaN(input)) return new Date(toInt(input));
    if (/^\d+$/.test(input)) return new Date(toInt(input));
    input = (input || '').trim().replace(/\.\d+/, '') // remove milliseconds
      .replace(/-/, '/').replace(/-/, '/')
      .replace(/(\d)T(\d)/, '$1 $2').replace(/Z/, ' UTC') // 2017-2-5T3:57:52Z -> 2017-2-5 3:57:52UTC
      .replace(/([\+\-]\d\d)\:?(\d\d)/, ' $1$2'); // -04:00 -> -0400
    return new Date(input);
  }
  // change f into int, remove Decimal. just for code compression
  function toInt(f) {
    return parseInt(f);
  }
  // format the diff second to *** time ago, with setting locale
  function formatDiff(diff, locale, defaultLocale) {
    // if locale is not exist, use defaultLocale.
    // if defaultLocale is not exist, use build-in `en`.
    // be sure of no error when locale is not exist.
    locale = locales[locale] ? locale : (locales[defaultLocale] ? defaultLocale : 'en');
    // if (! locales[locale]) locale = defaultLocale;
    var i = 0,
      agoin = diff < 0 ? 1 : 0; // timein or timeago
    diff = Math.abs(diff);

    for (; diff >= SEC_ARRAY[i] && i < SEC_ARRAY_LEN; i++) {
      diff /= SEC_ARRAY[i];
    }
    diff = toInt(diff);
    i *= 2;

    if (diff > (i === 0 ? 9 : 1)) i += 1;
    return locales[locale](diff, i)[agoin].replace('%s', diff);
  }
  // calculate the diff second between date to be formated an now date.
  function diffSec(date, nowDate) {
    nowDate = nowDate ? toDate(nowDate) : new Date();
    return (nowDate - toDate(date)) / 1000;
  }
  /**
   * nextInterval: calculate the next interval time.
   * - diff: the diff sec between now and date to be formated.
   *
   * What's the meaning?
   * diff = 61 then return 59
   * diff = 3601 (an hour + 1 second), then return 3599
   * make the interval with high performace.
  **/
  function nextInterval(diff) {
    var rst = 1, i = 0, d = Math.abs(diff);
    for (; diff >= SEC_ARRAY[i] && i < SEC_ARRAY_LEN; i++) {
      diff /= SEC_ARRAY[i];
      rst *= SEC_ARRAY[i];
    }
    // return leftSec(d, rst);
    d = d % rst;
    d = d ? rst - d : rst;
    return Math.ceil(d);
  }
  // get the datetime attribute, jQuery and DOM
  function getDateAttr(node) {
    if(node.dataset.timeago) return node.dataset.timeago; // data-timeago supported
    return getAttr(node, ATTR_DATETIME);
  }
  function getAttr(node, name) {
    if(node.getAttribute) return node.getAttribute(name); // native
    if(node.attr) return node.attr(name); // jquery
  }
  function setTidAttr(node, val) {
    if(node.setAttribute) return node.setAttribute(ATTR_DATA_TID, val); // native
    if(node.attr) return node.attr(ATTR_DATA_TID, val); // jquery
  }
  function getTidFromNode(node) {
    return getAttr(node, ATTR_DATA_TID);
  }
  /**
   * timeago: the function to get `timeago` instance.
   * - nowDate: the relative date, default is new Date().
   * - defaultLocale: the default locale, default is en. if your set it, then the `locale` parameter of format is not needed of you.
   *
   * How to use it?
   * var timeagoLib = require('timeago.js');
   * var timeago = timeagoLib(); // all use default.
   * var timeago = timeagoLib('2016-09-10'); // the relative date is 2016-09-10, so the 2016-09-11 will be 1 day ago.
   * var timeago = timeagoLib(null, 'zh_CN'); // set default locale is `zh_CN`.
   * var timeago = timeagoLib('2016-09-10', 'zh_CN'); // the relative date is 2016-09-10, and locale is zh_CN, so the 2016-09-11 will be 1天前.
  **/
  function Timeago(nowDate, defaultLocale) {
    this.nowDate = nowDate;
    // if do not set the defaultLocale, set it with `en`
    this.defaultLocale = defaultLocale || 'en'; // use default build-in locale
    /**
     * nextInterval: calculate the next interval time.
     * - diff: the diff sec between now and date to be formated.
     *
     * What's the meaning?
     * diff = 61 then return 59
     * diff = 3601 (an hour + 1 second), then return 3599
     * make the interval with high performace.
    **/
    // this.nextInterval = function(diff) { // for dev test
    //   var rst = 1, i = 0, d = Math.abs(diff);
    //   for (; diff >= SEC_ARRAY[i] && i < SEC_ARRAY_LEN; i++) {
    //     diff /= SEC_ARRAY[i];
    //     rst *= SEC_ARRAY[i];
    //   }
    //   // return leftSec(d, rst);
    //   d = d % rst;
    //   d = d ? rst - d : rst;
    //   return Math.ceil(d);
    // }; // for dev test
  }
  // what the timer will do
  Timeago.prototype.doRender = function(node, date, locale) {
    var diff = diffSec(date, this.nowDate),
      self = this,
      tid;
    // delete previously assigned timeout's id to node
    //delete timers[getTimeoutId(node)]; // if delete it from object, then `cancel()` may can not cancel all the timer.
    node.innerHTML = formatDiff(diff, locale, this.defaultLocale);
    // waiting %s seconds, do the next render
    timers[tid = setTimeout(function() {
      self.doRender(node, date, locale);
    }, Math.min(nextInterval(diff) * 1000, 0x7FFFFFFF))] = 0; // there is no need to save node in object.
    // set attribute date-tid
    setTidAttr(node, tid);
  };
  /**
   * format: format the date to *** time ago, with setting or default locale
   * - date: the date / string / timestamp to be formated
   * - locale: the formated string's locale name, e.g. en / zh_CN
   *
   * How to use it?
   * var timeago = require('timeago.js')();
   * timeago.format(new Date(), 'pl'); // Date instance
   * timeago.format('2016-09-10', 'fr'); // formated date string
   * timeago.format(1473473400269); // timestamp with ms
  **/
  Timeago.prototype.format = function(date, locale) {
    return formatDiff(diffSec(date, this.nowDate), locale, this.defaultLocale);
  };
  /**
   * render: render the DOM real-time.
   * - nodes: which nodes will be rendered.
   * - locale: the locale name used to format date.
   *
   * How to use it?
   * var timeago = new require('timeago.js')();
   * // 1. javascript selector
   * timeago.render(document.querySelectorAll('.need_to_be_rendered'));
   * // 2. use jQuery selector
   * timeago.render($('.need_to_be_rendered'), 'pl');
   *
   * Notice: please be sure the dom has attribute `datetime`.
  **/
  Timeago.prototype.render = function(nodes, locale) {
    if (nodes.length === undefined) nodes = [nodes];
    for (var i = 0, len = nodes.length; i < len; i++) {
      this.doRender(nodes[i], getDateAttr(nodes[i]), locale); // render item
    }
  };
  /**
   * setLocale: set the default locale name.
   *
   * How to use it?
   * var timeago = require('timeago.js');
   * timeago = new timeago();
   * timeago.setLocale('fr');
  **/
  Timeago.prototype.setLocale = function(locale) {
    this.defaultLocale = locale;
  };
  /**
   * timeago: the function to get `timeago` instance.
   * - nowDate: the relative date, default is new Date().
   * - defaultLocale: the default locale, default is en. if your set it, then the `locale` parameter of format is not needed of you.
   *
   * How to use it?
   * var timeagoLib = require('timeago.js');
   * var timeago = timeagoLib(); // all use default.
   * var timeago = timeagoLib('2016-09-10'); // the relative date is 2016-09-10, so the 2016-09-11 will be 1 day ago.
   * var timeago = timeagoLib(null, 'zh_CN'); // set default locale is `zh_CN`.
   * var timeago = timeagoLib('2016-09-10', 'zh_CN'); // the relative date is 2016-09-10, and locale is zh_CN, so the 2016-09-11 will be 1天前.
   **/
  function timeagoFactory(nowDate, defaultLocale) {
    return new Timeago(nowDate, defaultLocale);
  }
  /**
   * register: register a new language locale
   * - locale: locale name, e.g. en / zh_CN, notice the standard.
   * - localeFunc: the locale process function
   *
   * How to use it?
   * var timeagoLib = require('timeago.js');
   *
   * timeagoLib.register('the locale name', the_locale_func);
   * // or
   * timeagoLib.register('pl', require('timeago.js/locales/pl'));
   **/
  timeagoFactory.register = function(locale, localeFunc) {
    locales[locale] = localeFunc;
  };

  /**
   * cancel: cancels one or all the timers which are doing real-time render.
   *
   * How to use it?
   * For canceling all the timers:
   * var timeagoFactory = require('timeago.js');
   * var timeago = timeagoFactory();
   * timeago.render(document.querySelectorAll('.need_to_be_rendered'));
   * timeagoFactory.cancel(); // will stop all the timers, stop render in real time.
   *
   * For canceling single timer on specific node:
   * var timeagoFactory = require('timeago.js');
   * var timeago = timeagoFactory();
   * var nodes = document.querySelectorAll('.need_to_be_rendered');
   * timeago.render(nodes);
   * timeagoFactory.cancel(nodes[0]); // will clear a timer attached to the first node, stop render in real time.
   **/
  timeagoFactory.cancel = function(node) {
    var tid;
    // assigning in if statement to save space
    if (node && (tid = getTidFromNode(node))) {
      clearTimeout(tid);
      delete timers[tid];
    } else {
      for (tid in timers) clearTimeout(tid);
      timers = {};
    }
  };

  return timeagoFactory;
});
<commit_msg>Fix memory leak (#115)<commit_after>/**
 * Copyright (c) 2016 hustcc
 * License: MIT
 * Version: %%GULP_INJECT_VERSION%%
 * https://github.com/hustcc/timeago.js
**/
/* jshint expr: true */
!function (root, factory) {
  if (typeof module === 'object' && module.exports) {
    module.exports = factory(root); // nodejs support
    module.exports['default'] = module.exports; // es6 support
  }
  else
    root.timeago = factory(root);
}(typeof window !== 'undefined' ? window : this,
function () {
  var indexMapEn = 'second_minute_hour_day_week_month_year'.split('_'),
    indexMapZh = '秒_分钟_小时_天_周_月_年'.split('_'),
    // build-in locales: en & zh_CN
    locales = {
      'en': function(number, index) {
        if (index === 0) return ['just now', 'right now'];
        var unit = indexMapEn[parseInt(index / 2)];
        if (number > 1) unit += 's';
        return [number + ' ' + unit + ' ago', 'in ' + number + ' ' + unit];
      },
      'zh_CN': function(number, index) {
        if (index === 0) return ['刚刚', '片刻后'];
        var unit = indexMapZh[parseInt(index / 2)];
        return [number + unit + '前', number + unit + '后'];
      }
    },
    // second, minute, hour, day, week, month, year(365 days)
    SEC_ARRAY = [60, 60, 24, 7, 365/7/12, 12],
    SEC_ARRAY_LEN = 6,
    ATTR_DATETIME = 'datetime',
    ATTR_DATA_TID = 'data-tid',
    timers = {}; // real-time render timers

  // format Date / string / timestamp to Date instance.
  function toDate(input) {
    if (input instanceof Date) return input;
    if (!isNaN(input)) return new Date(toInt(input));
    if (/^\d+$/.test(input)) return new Date(toInt(input));
    input = (input || '').trim().replace(/\.\d+/, '') // remove milliseconds
      .replace(/-/, '/').replace(/-/, '/')
      .replace(/(\d)T(\d)/, '$1 $2').replace(/Z/, ' UTC') // 2017-2-5T3:57:52Z -> 2017-2-5 3:57:52UTC
      .replace(/([\+\-]\d\d)\:?(\d\d)/, ' $1$2'); // -04:00 -> -0400
    return new Date(input);
  }
  // change f into int, remove Decimal. just for code compression
  function toInt(f) {
    return parseInt(f);
  }
  // format the diff second to *** time ago, with setting locale
  function formatDiff(diff, locale, defaultLocale) {
    // if locale is not exist, use defaultLocale.
    // if defaultLocale is not exist, use build-in `en`.
    // be sure of no error when locale is not exist.
    locale = locales[locale] ? locale : (locales[defaultLocale] ? defaultLocale : 'en');
    // if (! locales[locale]) locale = defaultLocale;
    var i = 0,
      agoin = diff < 0 ? 1 : 0; // timein or timeago
    diff = Math.abs(diff);

    for (; diff >= SEC_ARRAY[i] && i < SEC_ARRAY_LEN; i++) {
      diff /= SEC_ARRAY[i];
    }
    diff = toInt(diff);
    i *= 2;

    if (diff > (i === 0 ? 9 : 1)) i += 1;
    return locales[locale](diff, i)[agoin].replace('%s', diff);
  }
  // calculate the diff second between date to be formated an now date.
  function diffSec(date, nowDate) {
    nowDate = nowDate ? toDate(nowDate) : new Date();
    return (nowDate - toDate(date)) / 1000;
  }
  /**
   * nextInterval: calculate the next interval time.
   * - diff: the diff sec between now and date to be formated.
   *
   * What's the meaning?
   * diff = 61 then return 59
   * diff = 3601 (an hour + 1 second), then return 3599
   * make the interval with high performace.
  **/
  function nextInterval(diff) {
    var rst = 1, i = 0, d = Math.abs(diff);
    for (; diff >= SEC_ARRAY[i] && i < SEC_ARRAY_LEN; i++) {
      diff /= SEC_ARRAY[i];
      rst *= SEC_ARRAY[i];
    }
    // return leftSec(d, rst);
    d = d % rst;
    d = d ? rst - d : rst;
    return Math.ceil(d);
  }
  // get the datetime attribute, jQuery and DOM
  function getDateAttr(node) {
    if(node.dataset.timeago) return node.dataset.timeago; // data-timeago supported
    return getAttr(node, ATTR_DATETIME);
  }
  function getAttr(node, name) {
    if(node.getAttribute) return node.getAttribute(name); // native
    if(node.attr) return node.attr(name); // jquery
  }
  function setTidAttr(node, val) {
    if(node.setAttribute) return node.setAttribute(ATTR_DATA_TID, val); // native
    if(node.attr) return node.attr(ATTR_DATA_TID, val); // jquery
  }
  function getTidFromNode(node) {
    return getAttr(node, ATTR_DATA_TID);
  }
  /**
   * timeago: the function to get `timeago` instance.
   * - nowDate: the relative date, default is new Date().
   * - defaultLocale: the default locale, default is en. if your set it, then the `locale` parameter of format is not needed of you.
   *
   * How to use it?
   * var timeagoLib = require('timeago.js');
   * var timeago = timeagoLib(); // all use default.
   * var timeago = timeagoLib('2016-09-10'); // the relative date is 2016-09-10, so the 2016-09-11 will be 1 day ago.
   * var timeago = timeagoLib(null, 'zh_CN'); // set default locale is `zh_CN`.
   * var timeago = timeagoLib('2016-09-10', 'zh_CN'); // the relative date is 2016-09-10, and locale is zh_CN, so the 2016-09-11 will be 1天前.
  **/
  function Timeago(nowDate, defaultLocale) {
    this.nowDate = nowDate;
    // if do not set the defaultLocale, set it with `en`
    this.defaultLocale = defaultLocale || 'en'; // use default build-in locale
    /**
     * nextInterval: calculate the next interval time.
     * - diff: the diff sec between now and date to be formated.
     *
     * What's the meaning?
     * diff = 61 then return 59
     * diff = 3601 (an hour + 1 second), then return 3599
     * make the interval with high performace.
    **/
    // this.nextInterval = function(diff) { // for dev test
    //   var rst = 1, i = 0, d = Math.abs(diff);
    //   for (; diff >= SEC_ARRAY[i] && i < SEC_ARRAY_LEN; i++) {
    //     diff /= SEC_ARRAY[i];
    //     rst *= SEC_ARRAY[i];
    //   }
    //   // return leftSec(d, rst);
    //   d = d % rst;
    //   d = d ? rst - d : rst;
    //   return Math.ceil(d);
    // }; // for dev test
  }
  // what the timer will do
  Timeago.prototype.doRender = function(node, date, locale) {
    var diff = diffSec(date, this.nowDate),
      self = this,
      tid;
    // delete previously assigned timeout's id to node
    node.innerHTML = formatDiff(diff, locale, this.defaultLocale);
    // waiting %s seconds, do the next render
    timers[tid = setTimeout(function() {
      self.doRender(node, date, locale);
      delete timers[tid];
    }, Math.min(nextInterval(diff) * 1000, 0x7FFFFFFF))] = 0; // there is no need to save node in object.
    // set attribute date-tid
    setTidAttr(node, tid);
  };
  /**
   * format: format the date to *** time ago, with setting or default locale
   * - date: the date / string / timestamp to be formated
   * - locale: the formated string's locale name, e.g. en / zh_CN
   *
   * How to use it?
   * var timeago = require('timeago.js')();
   * timeago.format(new Date(), 'pl'); // Date instance
   * timeago.format('2016-09-10', 'fr'); // formated date string
   * timeago.format(1473473400269); // timestamp with ms
  **/
  Timeago.prototype.format = function(date, locale) {
    return formatDiff(diffSec(date, this.nowDate), locale, this.defaultLocale);
  };
  /**
   * render: render the DOM real-time.
   * - nodes: which nodes will be rendered.
   * - locale: the locale name used to format date.
   *
   * How to use it?
   * var timeago = new require('timeago.js')();
   * // 1. javascript selector
   * timeago.render(document.querySelectorAll('.need_to_be_rendered'));
   * // 2. use jQuery selector
   * timeago.render($('.need_to_be_rendered'), 'pl');
   *
   * Notice: please be sure the dom has attribute `datetime`.
  **/
  Timeago.prototype.render = function(nodes, locale) {
    if (nodes.length === undefined) nodes = [nodes];
    for (var i = 0, len = nodes.length; i < len; i++) {
      this.doRender(nodes[i], getDateAttr(nodes[i]), locale); // render item
    }
  };
  /**
   * setLocale: set the default locale name.
   *
   * How to use it?
   * var timeago = require('timeago.js');
   * timeago = new timeago();
   * timeago.setLocale('fr');
  **/
  Timeago.prototype.setLocale = function(locale) {
    this.defaultLocale = locale;
  };
  /**
   * timeago: the function to get `timeago` instance.
   * - nowDate: the relative date, default is new Date().
   * - defaultLocale: the default locale, default is en. if your set it, then the `locale` parameter of format is not needed of you.
   *
   * How to use it?
   * var timeagoLib = require('timeago.js');
   * var timeago = timeagoLib(); // all use default.
   * var timeago = timeagoLib('2016-09-10'); // the relative date is 2016-09-10, so the 2016-09-11 will be 1 day ago.
   * var timeago = timeagoLib(null, 'zh_CN'); // set default locale is `zh_CN`.
   * var timeago = timeagoLib('2016-09-10', 'zh_CN'); // the relative date is 2016-09-10, and locale is zh_CN, so the 2016-09-11 will be 1天前.
   **/
  function timeagoFactory(nowDate, defaultLocale) {
    return new Timeago(nowDate, defaultLocale);
  }
  /**
   * register: register a new language locale
   * - locale: locale name, e.g. en / zh_CN, notice the standard.
   * - localeFunc: the locale process function
   *
   * How to use it?
   * var timeagoLib = require('timeago.js');
   *
   * timeagoLib.register('the locale name', the_locale_func);
   * // or
   * timeagoLib.register('pl', require('timeago.js/locales/pl'));
   **/
  timeagoFactory.register = function(locale, localeFunc) {
    locales[locale] = localeFunc;
  };

  /**
   * cancel: cancels one or all the timers which are doing real-time render.
   *
   * How to use it?
   * For canceling all the timers:
   * var timeagoFactory = require('timeago.js');
   * var timeago = timeagoFactory();
   * timeago.render(document.querySelectorAll('.need_to_be_rendered'));
   * timeagoFactory.cancel(); // will stop all the timers, stop render in real time.
   *
   * For canceling single timer on specific node:
   * var timeagoFactory = require('timeago.js');
   * var timeago = timeagoFactory();
   * var nodes = document.querySelectorAll('.need_to_be_rendered');
   * timeago.render(nodes);
   * timeagoFactory.cancel(nodes[0]); // will clear a timer attached to the first node, stop render in real time.
   **/
  timeagoFactory.cancel = function(node) {
    var tid;
    // assigning in if statement to save space
    if (node && (tid = getTidFromNode(node))) {
      clearTimeout(tid);
      delete timers[tid];
    } else {
      for (tid in timers) clearTimeout(tid);
      timers = {};
    }
  };

  return timeagoFactory;
});
<|endoftext|>


<commit_before>'use strict';

const request = require('requestretry')
const utils = require('./utils')
const moment = require('moment-timezone')
const zone = "GMT"

const URL_BASE = 'https://track24.net'
const URL_PATH = '/ajax/tracking118.ajax.php'

const exportModule = {}

/**
 * Get Track24 tracker info
 * Async
 *
 * Design changes may break this code!!
 * @param id
 * @param callback(Error, Track24TrackerInfo)
 */
exportModule.getInfo = function (id, callback) {
    obtainInfo(URL_BASE + URL_PATH, id, callback)
}

exportModule.getInfoProxy = function (id, proxyUrl, callback) {
    obtainInfo(proxyUrl + URL_BASE, id, callback)
}

/**
 * Get info from tracker request
 *
 * @param action
 * @param id
 * @param cb
 */
function obtainInfo(action, id, cb) {
    request.post({
        url: action,
        form: {
            code: id,
            lng: 'en',
            type: 'update'
        },
        timeout: 20000,
        maxAttempts: 2,
    }, function (error, response, body) {
        if (error || response.statusCode != 200) {
            cb(utils.getError('DOWN'))
            return
        }

        let data = null
        try {
            data = JSON.parse(body)
        } catch (error) {
            console.log(error);
            return cb(utils.getError('PARSER'))
        }

        if (data.status == undefined ||
            data.status != 'ok' ||
            (data.data.events.length == 1 &&
            data.data.events[0].operationAttributeTranslated == 'The track code is added to the database Track24.ru for automatic monitoring.')) {
            cb(utils.getError('NO_DATA'))
            return
        }

        data.gearTrackID = id

        let entity = null
        try {
            entity = createTrackerEntity(data)
        } catch (error) {
            console.log(id, error)
            return cb(utils.getError('PARSER'))
        }

        if (entity != null) cb(null, entity)
    })
}

/**
 * Create tracker entity from object
 * @param html
 */
function createTrackerEntity(data) {

    let result = data.data

    return new TrackerInfo({
        attempts: 1,
        id: data.gearTrackID,
        origin: result.fromCountry,
        destiny: result.destinationCountry,
        states: result.events.map((elem) => {
            return {
                state: elem.operationAttributeTranslated,
                date: moment.tz(elem.operationDateTime, "DD.MM.YYYY HH:mm:ss", zone),
                area: elem.operationPlaceNameTranslated,
                service: elem.serviceName
            }
        }).filter(state => {
            if (state.state == 'The track code is added to the database Track24.ru for automatic monitoring.')
                return false
            return true
        }).sort((a, b) => {
            let dateA = moment(a.date),
                dateB = moment(b.date)

            if (dateA.isBefore(dateB))
                return 1

            return -1
        })
    })
}

/*
 |--------------------------------------------------------------------------
 | Entity
 |--------------------------------------------------------------------------
 */
function TrackerInfo(obj) {
    this.attempts = obj.attempts
    this.id = obj.id
    this.states = obj.states
    this.origin = obj.origin,
    this.destiny = obj.destiny
    this.trackerWebsite = "https://track24.net/?code=" + this.id
}

module.exports = exportModule<commit_msg>:hammer: Fixed proxy link<commit_after>'use strict';

const request = require('requestretry')
const utils = require('./utils')
const moment = require('moment-timezone')
const zone = "GMT"

const URL_BASE = 'https://track24.net'
const URL_PATH = '/ajax/tracking118.ajax.php'

const exportModule = {}

/**
 * Get Track24 tracker info
 * Async
 *
 * Design changes may break this code!!
 * @param id
 * @param callback(Error, Track24TrackerInfo)
 */
exportModule.getInfo = function (id, callback) {
    obtainInfo(URL_BASE + URL_PATH, id, callback)
}

exportModule.getInfoProxy = function (id, proxyUrl, callback) {
    obtainInfo(proxyUrl + URL_PATH, id, callback)
}

/**
 * Get info from tracker request
 *
 * @param action
 * @param id
 * @param cb
 */
function obtainInfo(action, id, cb) {
    request.post({
        url: action,
        form: {
            code: id,
            lng: 'en',
            type: 'update'
        },
        timeout: 20000,
        maxAttempts: 2,
    }, function (error, response, body) {
        if (error || response.statusCode != 200) {
            cb(utils.getError('DOWN'))
            return
        }

        let data = null
        try {
            data = JSON.parse(body)
        } catch (error) {
            console.log(error);
            return cb(utils.getError('PARSER'))
        }

        if (data.status == undefined ||
            data.status != 'ok' ||
            (data.data.events.length == 1 &&
            data.data.events[0].operationAttributeTranslated == 'The track code is added to the database Track24.ru for automatic monitoring.')) {
            cb(utils.getError('NO_DATA'))
            return
        }

        data.gearTrackID = id

        let entity = null
        try {
            entity = createTrackerEntity(data)
        } catch (error) {
            console.log(id, error)
            return cb(utils.getError('PARSER'))
        }

        if (entity != null) cb(null, entity)
    })
}

/**
 * Create tracker entity from object
 * @param html
 */
function createTrackerEntity(data) {

    let result = data.data

    return new TrackerInfo({
        attempts: 1,
        id: data.gearTrackID,
        origin: result.fromCountry,
        destiny: result.destinationCountry,
        states: result.events.map((elem) => {
            return {
                state: elem.operationAttributeTranslated,
                date: moment.tz(elem.operationDateTime, "DD.MM.YYYY HH:mm:ss", zone),
                area: elem.operationPlaceNameTranslated,
                service: elem.serviceName
            }
        }).filter(state => {
            if (state.state == 'The track code is added to the database Track24.ru for automatic monitoring.')
                return false
            return true
        }).sort((a, b) => {
            let dateA = moment(a.date),
                dateB = moment(b.date)

            if (dateA.isBefore(dateB))
                return 1

            return -1
        })
    })
}

/*
 |--------------------------------------------------------------------------
 | Entity
 |--------------------------------------------------------------------------
 */
function TrackerInfo(obj) {
    this.attempts = obj.attempts
    this.id = obj.id
    this.states = obj.states
    this.origin = obj.origin,
    this.destiny = obj.destiny
    this.trackerWebsite = "https://track24.net/?code=" + this.id
}

module.exports = exportModule<|endoftext|>


<commit_before>'use strict';

var assign       = require('es5-ext/object/assign')
  , forEach      = require('es5-ext/object/for-each')
  , isRegExp     = require('es5-ext/reg-exp/is-reg-exp')
  , startsWith   = require('es5-ext/string/#/starts-with')
  , d            = require('d/d')
  , autoBind     = require('d/auto-bind')
  , ee           = require('event-emitter/lib/core')
  , castAttr     = require('dom-ext/element/#/cast-attribute')
  , dispatchEvt  = require('dom-ext/html-element/#/dispatch-event-2')
  , mergeClass   = require('dom-ext/html-element/#/merge-class')
  , nextTickOnce = require('next-tick/lib/once')
  , htmlAttrs    = require('../_html-attributes')
  , eventOpts    = require('../_event-options')

  , defineProperty = Object.defineProperty
  , Input;

module.exports = Input = function (document, type/*, options*/) {
	var options = Object(arguments[2]), onChange = this.onChange.bind(this);
	this.document = document;
	this.type = type;
	this.onChange = nextTickOnce(onChange);
	this._resolveDbAttributes(options);
	this._render(options);
	this.dom._dbjsInput = this;
	if (options.name) this.name = options.name;
	if (options.dbOptions.required) this.required = true;
	forEach(options, function (value, name) {
		if (name === 'class') {
			mergeClass.call(this.dom, value);
		} else if (htmlAttrs[name] || startsWith.call(name, 'data-')) {
			castAttr.call(this.dom, name, value);
		} else if (name === 'control') {
			forEach(value, function (value, name) {
				this.castControlAttribute(name, value);
			}, this);
		} else {
			this.castControlAttribute(name, value);
		}
	}, this);
	onChange();
	if (this.control) {
		document.addEventListener('reset', this._onReset, false);
		this.control.addEventListener('change', this.onChange, false);
	}
};

ee(Object.defineProperties(Input.prototype, assign({
	_value: d(''),
	_name: d(''),
	_resolveDbAttributes: d(function (options) {
		if (this._dbAttributesResolved) return;
		options.dbOptions = Object(options.dbOptions);
		forEach(this.dbAttributes, function (name, dbName) {
			var value;
			if (!name) return;
			if (name === true) name = dbName;
			if (options[name] != null) return;
			if (options.dbOptions[dbName] != null) {
				value = options.dbOptions[dbName];
			} else if (this.type[dbName] != null) {
				value = this.type[dbName];
			} else {
				if (dbName === 'required') return;
				if (this.type[dbName] != null) value = this.type[dbName];
				else return;
			}
			options[name] = value;
		}, this);
		defineProperty(this, '_dbAttributesResolved', d(true));
	}),
	controlAttributes: d({ autofocus: true, disabled: true, tabindex: true }),
	dbAttributes: d({}),
	changed: d(false),
	_required: d(false),
	valid: d(false),
	_render: d(function () {
		this.control = this.dom = this.document.createElement('input');
	}),
	name: d.gs(function () {
		return this._name ? (this._name + this._indexString) : '';
	}, function (name) {
		this._name = name;
		name = this.name;
		if (name) this.control.setAttribute('name', name);
		else this.control.removeAttribute('name');
	}),
	_index: d(null),
	_indexString: d.gs(function () {
		return (this._index == null) ? '' : '[' + this._index + ']';
	}),
	index: d.gs(function () { return this._index; }, function (index) {
		index = (index == null) ? null : (index >>> 0);
		if (index === this._index) return;
		this._index = index;
		this.name = this._name;
	}),
	required: d.gs(function () { return this._required; }, function (value) {
		value = Boolean(value);
		if (this._required === value) return;
		this._required = value;
		this.castControlAttribute('required', value);
		this.onChange();
		this.emit('change:required', value);
	}),
	toDOM: d(function () { return this.dom; }),
	inputValue: d.gs(function () {
		return this.control.value;
	}, function (nu) {
		var old = this.inputValue;
		if (this._value !== nu) {
			this.control.setAttribute('value', this._value = nu);
		}
		if (nu !== old) {
			this.control.value = nu;
			try {
				dispatchEvt.call(this.control, 'change', eventOpts);
			} catch (ignore) {}
		} else {
			this.onChange();
		}
	}),
	value: d.gs(function () {
		return this.type.fromInputValue(this.inputValue);
	}, function (value) {
		value = this.type.toInputValue(value);
		if (value == null) value = '';
		this.inputValue = value;
	}),
	castControlAttribute: d(function (name, value) {
		if (name === 'class') {
			mergeClass.call(this.control, value);
		} else if (!this.controlAttributes[name] && !htmlAttrs[name] &&
			!startsWith.call(name, 'data-')) {
			return;
		}
		if (isRegExp(value)) value = value.source.slice(1, -1);
		castAttr.call(this.control, name, value);
	}),
	destroy: d(function () {
		this.document.removeEventListener('reset', this._onReset, false);
		this.emit('destroy');
	}),
	onChange: d(function () {
		var value, inputValue, changed, valid, emitChanged, emitValid;
		inputValue = this.inputValue;
		value = this.value;
		changed = (inputValue !== this._value);
		if (value != null) valid = true;
		else if (this.required) valid = false;
		else valid = ((inputValue == null) || !inputValue.trim());

		if (this.changed !== changed) {
			this.changed = changed;
			emitChanged = true;
		}
		if (this.valid !== valid) {
			this.valid = valid;
			emitValid = true;
		}

		this.emit('change', value);
		if (emitChanged) this.emit('change:changed', this.changed);
		if (emitValid) this.emit('change:valid', this.valid);
	})
}, autoBind({
	_onReset: d(function (e) {
		if (e.target !== this.control.form) return;
		this.inputValue = this._value;
	})
}))));
<commit_msg>-- lint<commit_after>'use strict';

var assign       = require('es5-ext/object/assign')
  , forEach      = require('es5-ext/object/for-each')
  , isRegExp     = require('es5-ext/reg-exp/is-reg-exp')
  , startsWith   = require('es5-ext/string/#/starts-with')
  , d            = require('d/d')
  , autoBind     = require('d/auto-bind')
  , ee           = require('event-emitter/lib/core')
  , castAttr     = require('dom-ext/element/#/cast-attribute')
  , dispatchEvt  = require('dom-ext/html-element/#/dispatch-event-2')
  , mergeClass   = require('dom-ext/html-element/#/merge-class')
  , nextTickOnce = require('next-tick/lib/once')
  , htmlAttrs    = require('../_html-attributes')
  , eventOpts    = require('../_event-options')

  , defineProperty = Object.defineProperty
  , Input;

module.exports = Input = function (document, type/*, options*/) {
	var options = Object(arguments[2]), onChange = this.onChange.bind(this);
	this.document = document;
	this.type = type;
	this.onChange = nextTickOnce(onChange);
	this._resolveDbAttributes(options);
	this._render(options);
	this.dom._dbjsInput = this;
	if (options.name) this.name = options.name;
	if (options.dbOptions.required) this.required = true;
	forEach(options, function (value, name) {
		if (name === 'class') {
			mergeClass.call(this.dom, value);
		} else if (htmlAttrs[name] || startsWith.call(name, 'data-')) {
			castAttr.call(this.dom, name, value);
		} else if (name === 'control') {
			forEach(value, function (value, name) {
				this.castControlAttribute(name, value);
			}, this);
		} else {
			this.castControlAttribute(name, value);
		}
	}, this);
	onChange();
	if (this.control) {
		document.addEventListener('reset', this._onReset, false);
		this.control.addEventListener('change', this.onChange, false);
	}
};

ee(Object.defineProperties(Input.prototype, assign({
	_value: d(''),
	_name: d(''),
	_resolveDbAttributes: d(function (options) {
		if (this._dbAttributesResolved) return;
		options.dbOptions = Object(options.dbOptions);
		forEach(this.dbAttributes, function (name, dbName) {
			var value;
			if (!name) return;
			if (name === true) name = dbName;
			if (options[name] != null) return;
			if (options.dbOptions[dbName] != null) {
				value = options.dbOptions[dbName];
			} else if (this.type[dbName] != null) {
				value = this.type[dbName];
			} else {
				if (dbName === 'required') return;
				if (this.type[dbName] != null) value = this.type[dbName];
				else return;
			}
			options[name] = value;
		}, this);
		defineProperty(this, '_dbAttributesResolved', d(true));
	}),
	controlAttributes: d({ autofocus: true, disabled: true, tabindex: true }),
	dbAttributes: d({}),
	changed: d(false),
	_required: d(false),
	valid: d(false),
	_render: d(function () {
		this.control = this.dom = this.document.createElement('input');
	}),
	name: d.gs(function () {
		return this._name ? (this._name + this._indexString) : '';
	}, function (name) {
		this._name = name;
		name = this.name;
		if (name) this.control.setAttribute('name', name);
		else this.control.removeAttribute('name');
	}),
	_index: d(null),
	_indexString: d.gs(function () {
		return (this._index == null) ? '' : '[' + this._index + ']';
	}),
	index: d.gs(function () { return this._index; }, function (index) {
		index = (index == null) ? null : (index >>> 0);
		if (index === this._index) return;
		this._index = index;
		this.name = this._name;
	}),
	required: d.gs(function () { return this._required; }, function (value) {
		value = Boolean(value);
		if (this._required === value) return;
		this._required = value;
		this.castControlAttribute('required', value);
		this.onChange();
		this.emit('change:required', value);
	}),
	toDOM: d(function () { return this.dom; }),
	inputValue: d.gs(function () {
		return this.control.value;
	}, function (nu) {
		var old = this.inputValue;
		if (this._value !== nu) {
			this.control.setAttribute('value', this._value = nu);
		}
		if (nu !== old) {
			this.control.value = nu;
			try {
				dispatchEvt.call(this.control, 'change', eventOpts);
			} catch (ignore) {}
		} else {
			this.onChange();
		}
	}),
	value: d.gs(function () {
		return this.type.fromInputValue(this.inputValue);
	}, function (value) {
		value = this.type.toInputValue(value);
		if (value == null) value = '';
		this.inputValue = value;
	}),
	castControlAttribute: d(function (name, value) {
		if (name === 'class') {
			mergeClass.call(this.control, value);
		} else if (!this.controlAttributes[name] && !htmlAttrs[name] &&
				!startsWith.call(name, 'data-')) {
			return;
		}
		if (isRegExp(value)) value = value.source.slice(1, -1);
		castAttr.call(this.control, name, value);
	}),
	destroy: d(function () {
		this.document.removeEventListener('reset', this._onReset, false);
		this.emit('destroy');
	}),
	onChange: d(function () {
		var value, inputValue, changed, valid, emitChanged, emitValid;
		inputValue = this.inputValue;
		value = this.value;
		changed = (inputValue !== this._value);
		if (value != null) valid = true;
		else if (this.required) valid = false;
		else valid = ((inputValue == null) || !inputValue.trim());

		if (this.changed !== changed) {
			this.changed = changed;
			emitChanged = true;
		}
		if (this.valid !== valid) {
			this.valid = valid;
			emitValid = true;
		}

		this.emit('change', value);
		if (emitChanged) this.emit('change:changed', this.changed);
		if (emitValid) this.emit('change:valid', this.valid);
	})
}, autoBind({
	_onReset: d(function (e) {
		if (e.target !== this.control.form) return;
		this.inputValue = this._value;
	})
}))));
<|endoftext|>


<commit_before>/**
 * Created by PAVEI on 30/09/2014.
 */

angular.module('ionic-img-lazy-load', []);

angular.module('ionic-img-lazy-load').directive(
    'scroller', function ($document, $rootScope, $timeout) {
        return {
            restrict: 'A',
            link: function ($scope, $element, $attributes) {

                var id = 0,
                    scrollTimeoutId,
                    resizeTimeoutId;

                $scope.invoke = function () {
                    $rootScope.$broadcast('scrollEvent');
                }

                $element.bind('scroll', function (e) {

                    $timeout.cancel(scrollTimeoutId);

                    // wait for 200ms and then invoke listeners (simulates stop event)
                    scrollTimeoutId = $timeout($scope.invoke, 50);

                });

            }
        }
    });

angular.module('ionic-img-lazy-load').directive(
    'imageLazySrc', function ($document) {
        return {
            restrict: 'A',
            link: function ($scope, $element, $attributes) {
                var listenerRemover;

                var deregistration = $scope.$on('scrollEvent', function () {
                        console.log('scroll');
                        if (isInView()) {
                            $element[0].src = $attributes.imageLazySrc; // set src attribute on element (it will load image)
                            deregistration();
                        }
                    }
                );

                function isInView() {

                    var clientHeight = $document[0].documentElement.clientHeight;
                    var clientWidth = $document[0].documentElement.clientWidth;
                    var imageRect = $element[0].getBoundingClientRect();
                    return  (imageRect.top >= 0 && imageRect.bottom <= clientHeight) && (imageRect.left >= 0 && imageRect.right <= clientWidth);
                }

                // bind listener
                // listenerRemover = scrollAndResizeListener.bindListener(isInView);

                // unbind event listeners if element was destroyed
                // it happens when you change view, etc
                $element.on('$destroy', function () {
                    deregistration();
                });

                // explicitly call scroll listener (because, some images are in viewport already and we haven't scrolled yet)
                if (isInView()) {
                    $element[0].src = $attributes.imageLazySrc; // set src attribute on element (it will load image)
                    deregistration();
                }
            }
        }
    }
);<commit_msg>Adjust for codepen<commit_after>/**
 * Created by PAVEI on 30/09/2014.
 */

angular.module('ionic-img-lazy-load', []);

angular.module('ionic-img-lazy-load').directive(
    'scroller', function ($rootScope, $timeout) {
        return {
            restrict: 'A',
            link: function ($scope, $element) {

                var scrollTimeoutId = 0;

                $scope.invoke = function () {
                    $rootScope.$broadcast('scrollEvent');
                };

                $element.bind('scroll', function () {

                    $timeout.cancel(scrollTimeoutId);

                    // wait for 200ms and then invoke listeners (simulates stop event)
                    scrollTimeoutId = $timeout($scope.invoke, 50);

                });

            }
        };
    });

angular.module('ionic-img-lazy-load').directive(
    'imageLazySrc', function ($document) {
        return {
            restrict: 'A',
            link: function ($scope, $element, $attributes) {

                var deregistration = $scope.$on('scrollEvent', function () {
                        console.log('scroll');
                        if (isInView()) {
                            $element[0].src = $attributes.imageLazySrc; // set src attribute on element (it will load image)
                            deregistration();
                        }
                    }
                );

                function isInView() {

                    var clientHeight = $document[0].documentElement.clientHeight;
                    var clientWidth = $document[0].documentElement.clientWidth;
                    var imageRect = $element[0].getBoundingClientRect();
                    return  (imageRect.top >= 0 && imageRect.bottom <= clientHeight) && (imageRect.left >= 0 && imageRect.right <= clientWidth);
                }

                // bind listener
                // listenerRemover = scrollAndResizeListener.bindListener(isInView);

                // unbind event listeners if element was destroyed
                // it happens when you change view, etc
                $element.on('$destroy', function () {
                    deregistration();
                });

                // explicitly call scroll listener (because, some images are in viewport already and we haven't scrolled yet)
                if (isInView()) {
                    $element[0].src = $attributes.imageLazySrc; // set src attribute on element (it will load image)
                    deregistration();
                }
            }
        };
    }
);<|endoftext|>