<commit_before>// ==UserScript==
// @name          I am over 18
// @namespace     http://tsai.it/project/gmscripts/i-am-over-18/
// @homepageURL   https://github.com/tsaiid/gm-i-am-over-18
// @version       0.2.20160802
// @description   Automatically agrees the "I'm over 18" declaration. The current supported sites are primarily in Taiwan.
// @icon          https://github.com/tsaiid/gm-i-am-over-18/raw/master/icon48.png
// @icon64        https://github.com/tsaiid/gm-i-am-over-18/raw/master/icon64.png
// @license       MIT License (Expat)
// @copyright     2016, I-Ta Tsai (http://tsai.it/)
// @include       https://www.ptt.cc/*
// @include       http://www.ettoday.net/*
// @include       http://star.ettoday.net/*
// @include       http://*.playno1.com/*
// @include       http://*.eyny.com/*
// @include       http://ck101.com/*
// @include       http://www.mycould.com/*
// @include       http://kickass.socialtorrent.net/*
// @include       http://katproxy.com/*
// @include       http://www.xvideos.com/*
// @include       http://video.jav101.com/*
// @include       http://*.blogspot.tw/*
// @include       http://www.appledaily.com.tw/*
// @include       http://www.kocpc.com.tw/*
// @include       http://www.myfreecams.com/*
// @include       http://www.ibeauty.tw/*
// @require       https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js
// @require       https://gist.githubusercontent.com/BrockA/2625891/raw/waitForKeyElements.js
// @grant         none
// ==/UserScript==

window.addEventListener('load', function() { // wait until page loaded

  this.$ = this.jQuery = jQuery.noConflict(true);

  var url = window.location.href;

  function clickToContinue (jNodes) {
      console.log(jNodes);
      console.log(jNodes.context);
      console.log(jNodes.selector);
      jNodes[0].click();
  }

  // for ptt
  if (url.match(/www\.ptt\.cc/) && $('button:contains("我同意，我已年滿十八歲")').length) {
    $('button:contains("我同意，我已年滿十八歲")').click();
  }

  // for ettoday
  if (url.match(/www|star\.ettoday\.net/) && $('.adult-box').length && $('.adult-box').is(':visible')) {
    $('.adult-box').hide();
  }

  // for playno1
  if (url.match(/\.playno1\.com/)) {
    console.log("play01");
    waitForKeyElements (
      'button:contains("我已滿18歲 進入")',
          clickToContinue
    );
  }

  // for eyny, mycould
  if (url.match(/\.eyny\.com|www\.mycould\.com/) && $('input[value*="是，我已年滿18歲。"]').length) {
    $('input[value*="是，我已年滿18歲。"]').click();
  }

  // for ck101
  if (url.match(/ck101\.com/)) {
      console.log("ck101");
      waitForKeyElements (
      "#fwin_dialog_submit",
          clickToContinue
      );
  }

  // for kickass
  if (url.match(/kickass\.socialtorrent\.net|katproxy\.com/) && $('button:contains("Yes, let me see it")').length) {
    $('button:contains("Yes, let me see it")').click();
  }

  // for xvideos
  if ($('#disclamer_message').length) {
    $('#disclamer_message span:contains("ENTER")').parent().click();
  }

  // for jav101
  if (url.match(/video\.jav101\.com/) && $('a.agreeBtn').length) {
    $('a.agreeBtn').click();
  }

  // blogspot
  // from https://gist.github.com/obeattie/362589
  if (url.match(/\.blogspot\.tw/)) {
    var overlay = document.getElementById('injected-iframe');
    if (overlay) {
      var nextSibling = overlay.nextElementSibling;
      if (nextSibling.tagName == 'STYLE') nextSibling.parentElement.removeChild(nextSibling);
      overlay.parentElement.removeChild(overlay);
    }
  }

  // appledaily
  if (url.match(/www\.appledaily\.com\.tw/)) {
      console.log("appledaily");
      waitForKeyElements (
      "#popup_18 a.yes",
          clickToContinue
      );
  }

  // www.kocpc.com.tw
  if (url.match(/www\.kocpc\.com\.tw/)) {
      console.log("kocpc");
      waitForKeyElements (
      "button.ox18B",
          clickToContinue
      );
  }

  // myfreecams
  if (url.match(/www\.myfreecams\.com/)) {
      console.log("myfreecams");
      waitForKeyElements (
      "#enter_desktop",
          clickToContinue
      );
  }

  // ibeauty
  if (url.match(/www\.ibeauty\.tw/)) {
      console.log("ibeauty");
      waitForKeyElements (
        ".warningWp .warningBtn .btnYes",
        clickToContinue
      );
  }

}, false);
<commit_msg>jQuery.noConflict workaround should not be executed in the window load event listener. This may related with issue #1.<commit_after>// ==UserScript==
// @name          I am over 18
// @namespace     http://tsai.it/project/gmscripts/i-am-over-18/
// @homepageURL   https://github.com/tsaiid/gm-i-am-over-18
// @version       0.2.20160821
// @description   Automatically agrees the "I'm over 18" declaration. The current supported sites are primarily in Taiwan.
// @icon          https://github.com/tsaiid/gm-i-am-over-18/raw/master/icon48.png
// @icon64        https://github.com/tsaiid/gm-i-am-over-18/raw/master/icon64.png
// @license       MIT License (Expat)
// @copyright     2016, I-Ta Tsai (http://tsai.it/)
// @include       https://www.ptt.cc/*
// @include       http://www.ettoday.net/*
// @include       http://star.ettoday.net/*
// @include       http://*.playno1.com/*
// @include       http://*.eyny.com/*
// @include       http://ck101.com/*
// @include       http://www.mycould.com/*
// @include       http://kickass.socialtorrent.net/*
// @include       http://katproxy.com/*
// @include       http://www.xvideos.com/*
// @include       http://video.jav101.com/*
// @include       http://*.blogspot.tw/*
// @include       http://www.appledaily.com.tw/*
// @include       http://www.kocpc.com.tw/*
// @include       http://www.myfreecams.com/*
// @include       http://www.ibeauty.tw/*
// @require       https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js
// @require       https://gist.githubusercontent.com/BrockA/2625891/raw/waitForKeyElements.js
// @grant         none
// ==/UserScript==

this.$ = this.jQuery = jQuery.noConflict(true);

window.addEventListener('load', function() { // wait until page loaded

  var url = window.location.href;

  function clickToContinue (jNodes) {
      console.log(jNodes);
      console.log(jNodes.context);
      console.log(jNodes.selector);
      jNodes[0].click();
  }

  // for ptt
  if (url.match(/www\.ptt\.cc/) && $('button:contains("我同意，我已年滿十八歲")').length) {
    $('button:contains("我同意，我已年滿十八歲")').click();
  }

  // for ettoday
  if (url.match(/www|star\.ettoday\.net/) && $('.adult-box').length && $('.adult-box').is(':visible')) {
    $('.adult-box').hide();
  }

  // for playno1
  if (url.match(/\.playno1\.com/)) {
    console.log("play01");
    waitForKeyElements (
      'button:contains("我已滿18歲 進入")',
          clickToContinue
    );
  }

  // for eyny, mycould
  if (url.match(/\.eyny\.com|www\.mycould\.com/) && $('input[value*="是，我已年滿18歲。"]').length) {
    console.log(url);
    $('input[value*="是，我已年滿18歲。"]').click();
  }

  // for ck101
  if (url.match(/ck101\.com/)) {
      console.log("ck101");
      waitForKeyElements (
      "#fwin_dialog_submit",
          clickToContinue
      );
  }

  // for kickass
  if (url.match(/kickass\.socialtorrent\.net|katproxy\.com/) && $('button:contains("Yes, let me see it")').length) {
    $('button:contains("Yes, let me see it")').click();
  }

  // for xvideos
  if ($('#disclamer_message').length) {
    $('#disclamer_message span:contains("ENTER")').parent().click();
  }

  // for jav101
  if (url.match(/video\.jav101\.com/) && $('a.agreeBtn').length) {
    $('a.agreeBtn').click();
  }

  // blogspot
  // from https://gist.github.com/obeattie/362589
  if (url.match(/\.blogspot\.tw/)) {
    var overlay = document.getElementById('injected-iframe');
    if (overlay) {
      var nextSibling = overlay.nextElementSibling;
      if (nextSibling.tagName == 'STYLE') nextSibling.parentElement.removeChild(nextSibling);
      overlay.parentElement.removeChild(overlay);
    }
  }

  // appledaily
  if (url.match(/www\.appledaily\.com\.tw/)) {
      console.log("appledaily");
      waitForKeyElements (
      "#popup_18 a.yes",
          clickToContinue
      );
  }

  // www.kocpc.com.tw
  if (url.match(/www\.kocpc\.com\.tw/)) {
      console.log("kocpc");
      waitForKeyElements (
      "button.ox18B",
          clickToContinue
      );
  }

  // myfreecams
  if (url.match(/www\.myfreecams\.com/)) {
      console.log("myfreecams");
      waitForKeyElements (
      "#enter_desktop",
          clickToContinue
      );
  }

  // ibeauty
  if (url.match(/www\.ibeauty\.tw/)) {
      console.log("ibeauty");
      waitForKeyElements (
        ".warningWp .warningBtn .btnYes",
        clickToContinue
      );
  }

}, false);
<|endoftext|>


<commit_before>/**
 * @module gmf.raster.component
 */
import gmfRasterRasterService from 'gmf/raster/RasterService.js';
import googAsserts from 'goog/asserts.js';

/** @suppress {extraRequire} */
import ngeoMiscDebounce from 'ngeo/misc/debounce.js';

import * as olEvents from 'ol/events.js';

import 'bootstrap/js/dropdown.js';

/**
 * @type {!angular.Module}
 */
const exports = angular.module('gmfRasterComponent', [
  gmfRasterRasterService.module.name,
  ngeoMiscDebounce.name,
]);

exports.run(/* @ngInject */ ($templateCache) => {
  $templateCache.put('gmf/raster/widgetComponent', require('./widgetComponent.html'));
});

exports.value('gmfElevationwidgetTemplateUrl',
  /**
   * @param {!angular.Attributes} $attrs Attributes.
   * @return {string} The template url.
   */
  ($attrs) => {
    const templateUrl = $attrs['gmfElevationwidgetTemplateUrl'];
    return templateUrl !== undefined ? templateUrl :
      'gmf/raster/widgetComponent';
  });

/**
 * @param {!angular.Attributes} $attrs Attributes.
 * @param {!function(!angular.Attributes): string} gmfElevationwidgetTemplateUrl Template function.
 * @return {string} Template URL.
 * @ngInject
 */
function gmfElevationwidgetTemplateUrl($attrs, gmfElevationwidgetTemplateUrl) {
  return gmfElevationwidgetTemplateUrl($attrs);
}

/**
 * Provide a directive that set a value each 500ms with the elevation under the
 * mouse cursor position on the map. The value must come from the elevation
 * service of a c2cgeoportal server. The server's URL must be defined as
 * config value of the application.
 *
 * Example:
 *
 *      <span gmf-elevation
 *            gmf-elevation-active="elevationActive"
 *            gmf-elevation-elevation="elevationValue"
 *            gmf-elevation-layer="mainCtrl.elevationLayer"
 *            gmf-elevation-layersconfig="::mainCtrl.elevationLayersConfig"
 *            gmf-elevation-map="::mainCtrl.map">
 *            {{elevationValue}}
 *      </span>
 *
 *  For value in meter `elevationLayersConfig` can be an empty object, complex example:
 *
 *      elevationLayersConfig = {
 *          '<layer>': {
 *              'filter': 'ngeoUnitPrefix',
 *              'args': ['m²', 'square'],
 *              'postfix': '<notice>',
 *              'separator': ''
 *          }
 *      };
 *
 *
 * @htmlAttribute {boolean} gmf-elevation-active A boolean to set active or
 *     deactive the component.
 * @htmlAttribute {number} gmf-elevation-elevation The value to set with the
 *     elevation value.
 * @htmlAttribute {string} gmf-elevation-layer Elevation layer to use.
 * @htmlAttribute {Object.<string, gmf.raster.component.LayerConfig>} gmf-elevation-layersconfig Elevation layer configurations.
 * @htmlAttribute {ol.Map} gmf-elevation-map The map.
 * @return {angular.Directive} Directive Definition Object.
 * @ngdoc directive
 * @ngname gmfElevation
 */
exports.component_ = function() {
  return {
    restrict: 'A',
    controller: 'GmfElevationController as ctrl',
    bindToController: true,
    scope: {
      'active': '<gmfElevationActive',
      'elevation': '=gmfElevationElevation',
      'layersconfig': '=gmfElevationLayersconfig',
      'loading': '=?gmfElevationLoading',
      'layer': '<gmfElevationLayer',
      'map': '=gmfElevationMap'
    },
    link: (scope, element, attr) => {
      const ctrl = scope['ctrl'];

      // Watch active or not.
      scope.$watch(() => ctrl.active, function(active) {
        this.toggleActive_(active);
      }.bind(ctrl));

      // Watch current layer.
      scope.$watch(() => ctrl.layer, function(layer) {
        this.layer = layer;
        this.elevation = null;
      }.bind(ctrl));
    }
  };
};

exports.directive('gmfElevation', exports.component_);

/**
 * @param {!angular.Scope} $scope Scope.
 * @param {!angular.$filter} $filter Angular filter.
 * @param {!ngeox.miscDebounce} ngeoDebounce Ngeo debounce factory
 * @param {!gmf.raster.RasterService} gmfRaster Gmf Raster service
 * @constructor
 * @private
 * @ngInject
 * @ngdoc controller
 * @ngname gmfElevationController
 */
exports.Controller_ = function($scope, $filter, ngeoDebounce, gmfRaster) {

  /**
   * @type {!angular.$filter}
   * @private
   */
  this.filter_ = $filter;

  /**
   * @type {ngeox.miscDebounce}
   * @private
   */
  this.ngeoDebounce_ = ngeoDebounce;

  /**
   * @type {gmf.raster.RasterService}
   * @private
   */
  this.gmfRaster_ = gmfRaster;

  /**
   * @type {!Object.<string, gmf.raster.component.LayerConfig>}
   * @private
   */
  this.layersConfig;

  /**
   * @type {boolean}
   */
  this.active;

  /**
   * @type {!string|undefined}
   * @export
   */
  this.elevation;

  /**
   * @type {string}
   */
  this.layer;

  /**
   * @type {ol.Map}
   */
  this.map;

  /**
   * @type {Array.<ol.EventsKey>}
   * @private
   */
  this.listenerKeys_ = [];

  /**
   * @type {angular.Scope}
   * @private
   */
  this.scope_ = $scope;

  /**
   * @type {boolean}
   * @private
   */
  this.inViewport_ = false;

  /**
   * @type {boolean}
   * @export
   */
  this.loading = false;
};

/**
 * Active or deactive the request of the raster each 500 ms on pointermove.
 * @param {boolean} active true to make requests.
 * @private
 */
exports.Controller_.prototype.toggleActive_ = function(active) {
  this.elevation = undefined;
  if (active) {
    googAsserts.assert(this.listenerKeys_.length === 0);

    // Moving the mouse clears previously displayed elevation
    this.listenerKeys_.push(olEvents.listen(this.map, 'pointermove',
      function(e) {
        this.scope_.$apply(() => {
          this.inViewport_ = true;
          this.elevation = undefined;
          this.loading = false;
        });
      }, this));

    // Launch the elevation service request when the user stops moving the
    // mouse for less short delay
    this.listenerKeys_.push(olEvents.listen(this.map, 'pointermove',
      this.ngeoDebounce_(this.pointerStop_.bind(this), 500, true)
    ));

    this.listenerKeys_.push(olEvents.listen(this.map.getViewport(), 'mouseout', () => {
      this.scope_.$apply(() => {
        this.elevation = undefined;
        this.inViewport_ = false;
        this.loading = false;
      });
    }));
  } else {
    this.elevation = undefined;
    this.listenerKeys_.forEach(olEvents.unlistenByKey);
    this.listenerKeys_.length = 0;
  }
};

/**
 * Request data from a raster from a MapBrowserPointerEvent's coordinates.
 * Called when the user stopped moving the mouse for 500ms.
 * @param {ol.MapBrowserPointerEvent} e An ol map browser pointer event.
 * @private
 */
exports.Controller_.prototype.pointerStop_ = function(e) {
  if (this.inViewport_) {
    this.loading = true;
    const params = {
      'layers': this.layer
    };
    this.gmfRaster_.getRaster(e.coordinate, params).then(
      this.getRasterSuccess_.bind(this),
      this.getRasterError_.bind(this)
    );
  }
};

/**
 * @param {Object.<string, number>} resp Response of the get Raster service.
 * @private
 */
exports.Controller_.prototype.getRasterSuccess_ = function(resp) {
  googAsserts.assert(this.layer, 'A layer should be selected');
  const value = resp[this.layer];
  if (value !== undefined && value !== null) {
    const options = this.layersconfig[this.layer] || {};
    const filter = options.filter || 'number';
    const custom_args = options.args || [];
    const postfix = options.hasOwnProperty('postfix') ? options.postfix : 'm';
    const separator = postfix.length > 0 ?
      (options.hasOwnProperty('separator') ? options.separator : '\u00a0') : '';
    const args = Array.concat([value], custom_args);
    this.elevation = this.filter_(filter).apply(args) + separator + postfix;
  } else {
    this.elevation = undefined;
  }
  this.loading = false;
};

/**
 * @private
 */
exports.Controller_.prototype.getRasterError_ = function() {
  console.error('Error on getting the raster.');
  this.elevation = undefined;
  this.loading = false;
};

exports.controller('GmfElevationController', exports.Controller_);

/**
 * Provides a component which encapsulates the elevation component (see above)
 * in a button with dropdown menu to be included in a application directly.
 *
 * Example:
 *  <gmf-elevationwidget
 *      gmf-elevationwidget-map="::mainCtrl.map"
 *      gmf-elevationwidget-layers="::mainCtrl.elevationLayers"
 *      gmf-elevationwidget-layersconfig="::mainCtrl.elevationLayersConfig"
 *      gmf-elevationwidget-active="mainCtrl.showInfobar">
 *  </gmf-elevationwidget>
 *
 * @htmlAttribute {ol.Map} gmf-elevationwidget-map The map.
 * @htmlAttribute {Array.<string>} gmf-elevationwidget-layers The list of
 *     layers.
 * @htmlAttribute {boolean} gmf-elevationwidget-active Whether to activate the
 *     elevation component.
 *
 * @ngdoc component
 * @ngname gmfElevationwidget
 */
exports.widgetComponent_ = {
  controller: 'gmfElevationwidgetController as ctrl',
  bindings: {
    'map': '<gmfElevationwidgetMap',
    'layers': '<gmfElevationwidgetLayers',
    'layersconfig': '=gmfElevationwidgetLayersconfig',
    'active': '<gmfElevationwidgetActive'
  },
  templateUrl: gmfElevationwidgetTemplateUrl
};
exports.component('gmfElevationwidget', exports.widgetComponent_);

/**
 * @constructor
 * @private
 * @ngdoc controller
 */
exports.WidgetController_ = function() {
  /**
   * @type {!ol.Map}
   * @export
   */
  this.map;

  /**
   * @type {!Array.<string>}
   * @export
   */
  this.layers;

  /**
   * @type {!Object.<string, gmf.raster.component.LayerConfig>}
   * @private
   */
  this.layersconfig;

  /**
   * @type {boolean}
   * @export
   */
  this.active;

  /**
   * @type {string}
   * @export
   */
  this.selectedElevationLayer;
};

exports.WidgetController_.prototype.$onInit = function() {
  this.selectedElevationLayer = this.layers[0];
};

exports.controller('gmfElevationwidgetController', exports.WidgetController_);

/**
 * @typedef {{
 *     filter: (string|undefined),
 *     args: (Array.<string>|undefined),
 *     postfix: (string|undefined),
 *     separator: (string|undefined)
 * }}
 */
exports.LayerConfig;

export default exports;
<commit_msg>Use prototype for array concat<commit_after>/**
 * @module gmf.raster.component
 */
import gmfRasterRasterService from 'gmf/raster/RasterService.js';
import googAsserts from 'goog/asserts.js';

/** @suppress {extraRequire} */
import ngeoMiscDebounce from 'ngeo/misc/debounce.js';

import * as olEvents from 'ol/events.js';

import 'bootstrap/js/dropdown.js';

/**
 * @type {!angular.Module}
 */
const exports = angular.module('gmfRasterComponent', [
  gmfRasterRasterService.module.name,
  ngeoMiscDebounce.name,
]);

exports.run(/* @ngInject */ ($templateCache) => {
  $templateCache.put('gmf/raster/widgetComponent', require('./widgetComponent.html'));
});

exports.value('gmfElevationwidgetTemplateUrl',
  /**
   * @param {!angular.Attributes} $attrs Attributes.
   * @return {string} The template url.
   */
  ($attrs) => {
    const templateUrl = $attrs['gmfElevationwidgetTemplateUrl'];
    return templateUrl !== undefined ? templateUrl :
      'gmf/raster/widgetComponent';
  });

/**
 * @param {!angular.Attributes} $attrs Attributes.
 * @param {!function(!angular.Attributes): string} gmfElevationwidgetTemplateUrl Template function.
 * @return {string} Template URL.
 * @ngInject
 */
function gmfElevationwidgetTemplateUrl($attrs, gmfElevationwidgetTemplateUrl) {
  return gmfElevationwidgetTemplateUrl($attrs);
}

/**
 * Provide a directive that set a value each 500ms with the elevation under the
 * mouse cursor position on the map. The value must come from the elevation
 * service of a c2cgeoportal server. The server's URL must be defined as
 * config value of the application.
 *
 * Example:
 *
 *      <span gmf-elevation
 *            gmf-elevation-active="elevationActive"
 *            gmf-elevation-elevation="elevationValue"
 *            gmf-elevation-layer="mainCtrl.elevationLayer"
 *            gmf-elevation-layersconfig="::mainCtrl.elevationLayersConfig"
 *            gmf-elevation-map="::mainCtrl.map">
 *            {{elevationValue}}
 *      </span>
 *
 *  For value in meter `elevationLayersConfig` can be an empty object, complex example:
 *
 *      elevationLayersConfig = {
 *          '<layer>': {
 *              'filter': 'ngeoUnitPrefix',
 *              'args': ['m²', 'square'],
 *              'postfix': '<notice>',
 *              'separator': ''
 *          }
 *      };
 *
 *
 * @htmlAttribute {boolean} gmf-elevation-active A boolean to set active or
 *     deactive the component.
 * @htmlAttribute {number} gmf-elevation-elevation The value to set with the
 *     elevation value.
 * @htmlAttribute {string} gmf-elevation-layer Elevation layer to use.
 * @htmlAttribute {Object.<string, gmf.raster.component.LayerConfig>} gmf-elevation-layersconfig Elevation layer configurations.
 * @htmlAttribute {ol.Map} gmf-elevation-map The map.
 * @return {angular.Directive} Directive Definition Object.
 * @ngdoc directive
 * @ngname gmfElevation
 */
exports.component_ = function() {
  return {
    restrict: 'A',
    controller: 'GmfElevationController as ctrl',
    bindToController: true,
    scope: {
      'active': '<gmfElevationActive',
      'elevation': '=gmfElevationElevation',
      'layersconfig': '=gmfElevationLayersconfig',
      'loading': '=?gmfElevationLoading',
      'layer': '<gmfElevationLayer',
      'map': '=gmfElevationMap'
    },
    link: (scope, element, attr) => {
      const ctrl = scope['ctrl'];

      // Watch active or not.
      scope.$watch(() => ctrl.active, function(active) {
        this.toggleActive_(active);
      }.bind(ctrl));

      // Watch current layer.
      scope.$watch(() => ctrl.layer, function(layer) {
        this.layer = layer;
        this.elevation = null;
      }.bind(ctrl));
    }
  };
};

exports.directive('gmfElevation', exports.component_);

/**
 * @param {!angular.Scope} $scope Scope.
 * @param {!angular.$filter} $filter Angular filter.
 * @param {!ngeox.miscDebounce} ngeoDebounce Ngeo debounce factory
 * @param {!gmf.raster.RasterService} gmfRaster Gmf Raster service
 * @constructor
 * @private
 * @ngInject
 * @ngdoc controller
 * @ngname gmfElevationController
 */
exports.Controller_ = function($scope, $filter, ngeoDebounce, gmfRaster) {

  /**
   * @type {!angular.$filter}
   * @private
   */
  this.filter_ = $filter;

  /**
   * @type {ngeox.miscDebounce}
   * @private
   */
  this.ngeoDebounce_ = ngeoDebounce;

  /**
   * @type {gmf.raster.RasterService}
   * @private
   */
  this.gmfRaster_ = gmfRaster;

  /**
   * @type {!Object.<string, gmf.raster.component.LayerConfig>}
   * @private
   */
  this.layersConfig;

  /**
   * @type {boolean}
   */
  this.active;

  /**
   * @type {!string|undefined}
   * @export
   */
  this.elevation;

  /**
   * @type {string}
   */
  this.layer;

  /**
   * @type {ol.Map}
   */
  this.map;

  /**
   * @type {Array.<ol.EventsKey>}
   * @private
   */
  this.listenerKeys_ = [];

  /**
   * @type {angular.Scope}
   * @private
   */
  this.scope_ = $scope;

  /**
   * @type {boolean}
   * @private
   */
  this.inViewport_ = false;

  /**
   * @type {boolean}
   * @export
   */
  this.loading = false;
};

/**
 * Active or deactive the request of the raster each 500 ms on pointermove.
 * @param {boolean} active true to make requests.
 * @private
 */
exports.Controller_.prototype.toggleActive_ = function(active) {
  this.elevation = undefined;
  if (active) {
    googAsserts.assert(this.listenerKeys_.length === 0);

    // Moving the mouse clears previously displayed elevation
    this.listenerKeys_.push(olEvents.listen(this.map, 'pointermove',
      function(e) {
        this.scope_.$apply(() => {
          this.inViewport_ = true;
          this.elevation = undefined;
          this.loading = false;
        });
      }, this));

    // Launch the elevation service request when the user stops moving the
    // mouse for less short delay
    this.listenerKeys_.push(olEvents.listen(this.map, 'pointermove',
      this.ngeoDebounce_(this.pointerStop_.bind(this), 500, true)
    ));

    this.listenerKeys_.push(olEvents.listen(this.map.getViewport(), 'mouseout', () => {
      this.scope_.$apply(() => {
        this.elevation = undefined;
        this.inViewport_ = false;
        this.loading = false;
      });
    }));
  } else {
    this.elevation = undefined;
    this.listenerKeys_.forEach(olEvents.unlistenByKey);
    this.listenerKeys_.length = 0;
  }
};

/**
 * Request data from a raster from a MapBrowserPointerEvent's coordinates.
 * Called when the user stopped moving the mouse for 500ms.
 * @param {ol.MapBrowserPointerEvent} e An ol map browser pointer event.
 * @private
 */
exports.Controller_.prototype.pointerStop_ = function(e) {
  if (this.inViewport_) {
    this.loading = true;
    const params = {
      'layers': this.layer
    };
    this.gmfRaster_.getRaster(e.coordinate, params).then(
      this.getRasterSuccess_.bind(this),
      this.getRasterError_.bind(this)
    );
  }
};

/**
 * @param {Object.<string, number>} resp Response of the get Raster service.
 * @private
 */
exports.Controller_.prototype.getRasterSuccess_ = function(resp) {
  googAsserts.assert(this.layer, 'A layer should be selected');
  const value = resp[this.layer];
  if (value !== undefined && value !== null) {
    const options = this.layersconfig[this.layer] || {};
    const filter = options.filter || 'number';
    const custom_args = options.args || [];
    const postfix = options.hasOwnProperty('postfix') ? options.postfix : 'm';
    const separator = postfix.length > 0 ?
      (options.hasOwnProperty('separator') ? options.separator : '\u00a0') : '';
    const args = Array.prototype.concat([value], custom_args);
    this.elevation = this.filter_(filter).apply(args) + separator + postfix;
  } else {
    this.elevation = undefined;
  }
  this.loading = false;
};

/**
 * @private
 */
exports.Controller_.prototype.getRasterError_ = function() {
  console.error('Error on getting the raster.');
  this.elevation = undefined;
  this.loading = false;
};

exports.controller('GmfElevationController', exports.Controller_);

/**
 * Provides a component which encapsulates the elevation component (see above)
 * in a button with dropdown menu to be included in a application directly.
 *
 * Example:
 *  <gmf-elevationwidget
 *      gmf-elevationwidget-map="::mainCtrl.map"
 *      gmf-elevationwidget-layers="::mainCtrl.elevationLayers"
 *      gmf-elevationwidget-layersconfig="::mainCtrl.elevationLayersConfig"
 *      gmf-elevationwidget-active="mainCtrl.showInfobar">
 *  </gmf-elevationwidget>
 *
 * @htmlAttribute {ol.Map} gmf-elevationwidget-map The map.
 * @htmlAttribute {Array.<string>} gmf-elevationwidget-layers The list of
 *     layers.
 * @htmlAttribute {boolean} gmf-elevationwidget-active Whether to activate the
 *     elevation component.
 *
 * @ngdoc component
 * @ngname gmfElevationwidget
 */
exports.widgetComponent_ = {
  controller: 'gmfElevationwidgetController as ctrl',
  bindings: {
    'map': '<gmfElevationwidgetMap',
    'layers': '<gmfElevationwidgetLayers',
    'layersconfig': '=gmfElevationwidgetLayersconfig',
    'active': '<gmfElevationwidgetActive'
  },
  templateUrl: gmfElevationwidgetTemplateUrl
};
exports.component('gmfElevationwidget', exports.widgetComponent_);

/**
 * @constructor
 * @private
 * @ngdoc controller
 */
exports.WidgetController_ = function() {
  /**
   * @type {!ol.Map}
   * @export
   */
  this.map;

  /**
   * @type {!Array.<string>}
   * @export
   */
  this.layers;

  /**
   * @type {!Object.<string, gmf.raster.component.LayerConfig>}
   * @private
   */
  this.layersconfig;

  /**
   * @type {boolean}
   * @export
   */
  this.active;

  /**
   * @type {string}
   * @export
   */
  this.selectedElevationLayer;
};

exports.WidgetController_.prototype.$onInit = function() {
  this.selectedElevationLayer = this.layers[0];
};

exports.controller('gmfElevationwidgetController', exports.WidgetController_);

/**
 * @typedef {{
 *     filter: (string|undefined),
 *     args: (Array.<string>|undefined),
 *     postfix: (string|undefined),
 *     separator: (string|undefined)
 * }}
 */
exports.LayerConfig;

export default exports;
<|endoftext|>


<commit_before>
/* =========================================================================
 * Svelto - Widgets - Select
 * =========================================================================
 * Copyright (c) 2015-2016 Fabio Spampinato
 * Licensed under MIT (https://github.com/svelto/svelto/blob/master/LICENSE)
 * =========================================================================
 * @require core/browser/browser.js
 * @require widgets/popover/popover.js
 * ========================================================================= */

//TODO: Add support for selecting multiple options (with checkboxes maybe)
//TODO: Add an input field for searching through the options

(function ( $, _, Svelto, Widgets, Factory, Browser, Pointer, Colors ) {

  'use strict';

  /* CONFIG */

  let config = {
    name: 'select',
    plugin: true,
    selector: '.select',
    templates: {
      base: '<div class="popover select-popover card <%= o.size %> <%= o.color %> <%= o.css %> <%= o.guc %>">' +
              '
' +
                '<% for ( var i = 0, l = o.options.length; i < l; i++ ) { %>' +
                  '<% print ( self[ o.options[i].value ? "option" : "optgroup" ] ( { opt: o.options[i], color: o.color } ) ) %>' +
                '<% } %>' +
              '
' +
            '
',
      optgroup: '
' +
                  '<%= o.opt.prop %>' +
                '
',
      option: '<div class="button <%= o.color %>" data-value="<%= o.opt.prop %>">' +
                '<%= o.opt.value %>' +
              '
'
    },
    options: {
      native: Browser.is.touchDevice, // Don't show the popover and use the native select, by default on touch devices
      popover: {
        size: '',
        color: Colors.white,
        css: Widgets.Popover.config.options.classes.affixed + ' bordered'
      },
      classes: {
        selected: 'active highlighted highlight-left',
        affixed: Widgets.Popover.config.options.classes.affixed
      },
      datas: {
        value: 'value'
      },
      selectors: {
        select: 'select',
        option: 'option',
        valueholder: '.select-value, label',
        button: '.button'
      },
      callbacks: {
        change: _.noop,
        open: _.noop,
        close: _.noop
      }
    }
  };

  /* SELECT */

  class Select extends Widgets.Widget {

    /* SPECIAL */

    _variables () {

      this.$wrp = this.$element;
      this.$select = this.$wrp.find ( this.options.selectors.select );
      this.$options = this.$select.find ( this.options.selectors.option );
      this.$valueholder = this.$wrp.find ( this.options.selectors.valueholder ).first ();

      this.selectOptions = [];

      this.$popover = false;

    }

    _init () {

      this._updateValueholder ();

      if ( !this.options.native ) {

        this.$select.addClass ( this.options.classes.hidden );

        this.___selectOptions ();
        this.___popover ();

      }

    }

    _events () {

      this.___change ();

    }

    /* CHANGE */

    ___change () {

      this._on ( true, this.$select, 'change', this.__change );

    }

    __change () {

      this._update ();

      this._trigger ( 'change' );

    }

    /* BUTTON TAP */

    ___buttonTap () {

      if ( !Browser.is.touchDevice ) {

        /* BUTTON TAP */

        this._on ( this.$popover, Pointer.tap, this.options.selectors.button, this.__buttonTap );

      }

    }

    __buttonTap ( event ) {

      this.$popover.popover ( 'close' );

      this.set ( $(event.currentTarget).data ( this.options.datas.value ) );

    }

    /* OPTIONS */

    ___selectOptions () { //FIXME: Add support for arbitrary number of optgroups nesting levels

      let previousOptgroup;

      for ( let option of this.$options ) {

        let $option = $(option),
            $parent = $option.parent ();

        if ( $parent.is ( 'optgroup' ) ) {

          let currentOptgroup = $parent.attr ( 'label' );

          if ( currentOptgroup !== previousOptgroup ) {

            previousOptgroup = currentOptgroup;

            this.selectOptions.push ({
              prop: currentOptgroup
            });

          }

        }

        let value = $option.text ();

        if ( value ) {

          this.selectOptions.push ({
            value: $option.text (),
            prop: $option.attr ( 'value' )
          });

        }

      }

    }

    /* POPOVER */

    ___popover () {

      let html = this._template ( 'base', _.extend ( { guc: this.guc, options: this.selectOptions }, this.options.popover ) );

      this.$popover = $(html).appendTo ( this.$layout );
      this.$buttons = this.$popover.find ( this.options.selectors.button );

      this.$popover.popover ({
        positionate: {
          axis: 'y',
          strict: true
        },
        callbacks: {
          beforeopen: this.__setPopoverWidth.bind ( this ),
          open: this.__popoverOpen.bind ( this ),
          close: this.__popoverClose.bind ( this )
        }
      });

      this.$wrp.attr ( `data-${Widgets.Targeter.config.options.datas.target}`, '.' + this.guc ).popoverToggler ();

      this._updatePopover ();

    }

    __setPopoverWidth () {

      if ( this.$popover.is ( '.' + this.options.classes.affixed ) ) {

        this.$popover.css ( 'min-width', this.$wrp.outerWidth () );

      }

    }

    __popoverOpen () {

      this.___buttonTap ();

      this._trigger ( 'open' );

    }

    __popoverClose () {

      this._reset ();

      this.___change ();

      this._trigger ( 'close' );

    }

    /* UPDATE */

    _updateValueholder () {

      let value = this.$select.val ();

      if ( _.isString ( value ) && value.length ) {

        let $selectedOption = this.$options.filter ( `[value="${value}"]` ).first ();

        this.$valueholder.text ( $selectedOption.text () );

      }

    }

    _updatePopover () {

      this.$buttons.removeClass ( this.options.classes.selected );

      this.$buttons.filter ( '[data-' + this.options.datas.value + '="' + this.$select.val () + '"]' ).first ().addClass ( this.options.classes.selected );

    }

    _update () {

      this._updateValueholder ();

      if ( !Browser.is.touchDevice ) {

        this._updatePopover ();

      }

    }

    /* API */

    get () {

      return this.$select.val ();

    }

    set ( value ) {

      let $button = this.$buttons.filter ( '[data-' + this.options.datas.value + '="' + value + '"]' ).first ();

      if ( !$button.length ) return;

      this.$select.val ( value ).trigger ( 'change' );

    }

  }

  /* FACTORY */

  Factory.init ( Select, config, Widgets );

}( Svelto.$, Svelto._, Svelto, Svelto.Widgets, Svelto.Factory, Svelto.Browser, Svelto.Pointer, Svelto.Colors ));
<commit_msg>Preventing propagation of click on select's buttons<commit_after>
/* =========================================================================
 * Svelto - Widgets - Select
 * =========================================================================
 * Copyright (c) 2015-2016 Fabio Spampinato
 * Licensed under MIT (https://github.com/svelto/svelto/blob/master/LICENSE)
 * =========================================================================
 * @require core/browser/browser.js
 * @require widgets/popover/popover.js
 * ========================================================================= */

//TODO: Add support for selecting multiple options (with checkboxes maybe)
//TODO: Add an input field for searching through the options

(function ( $, _, Svelto, Widgets, Factory, Browser, Pointer, Colors ) {

  'use strict';

  /* CONFIG */

  let config = {
    name: 'select',
    plugin: true,
    selector: '.select',
    templates: {
      base: '<div class="popover select-popover card <%= o.size %> <%= o.color %> <%= o.css %> <%= o.guc %>">' +
              '
' +
                '<% for ( var i = 0, l = o.options.length; i < l; i++ ) { %>' +
                  '<% print ( self[ o.options[i].value ? "option" : "optgroup" ] ( { opt: o.options[i], color: o.color } ) ) %>' +
                '<% } %>' +
              '
' +
            '
',
      optgroup: '
' +
                  '<%= o.opt.prop %>' +
                '
',
      option: '<div class="button <%= o.color %>" data-value="<%= o.opt.prop %>">' +
                '<%= o.opt.value %>' +
              '
'
    },
    options: {
      native: Browser.is.touchDevice, // Don't show the popover and use the native select, by default on touch devices
      popover: {
        size: '',
        color: Colors.white,
        css: Widgets.Popover.config.options.classes.affixed + ' bordered'
      },
      classes: {
        selected: 'active highlighted highlight-left',
        affixed: Widgets.Popover.config.options.classes.affixed
      },
      datas: {
        value: 'value'
      },
      selectors: {
        select: 'select',
        option: 'option',
        valueholder: '.select-value, label',
        button: '.button'
      },
      callbacks: {
        change: _.noop,
        open: _.noop,
        close: _.noop
      }
    }
  };

  /* SELECT */

  class Select extends Widgets.Widget {

    /* SPECIAL */

    _variables () {

      this.$wrp = this.$element;
      this.$select = this.$wrp.find ( this.options.selectors.select );
      this.$options = this.$select.find ( this.options.selectors.option );
      this.$valueholder = this.$wrp.find ( this.options.selectors.valueholder ).first ();

      this.selectOptions = [];

      this.$popover = false;

    }

    _init () {

      this._updateValueholder ();

      if ( !this.options.native ) {

        this.$select.addClass ( this.options.classes.hidden );

        this.___selectOptions ();
        this.___popover ();

      }

    }

    _events () {

      this.___change ();

    }

    /* CHANGE */

    ___change () {

      this._on ( true, this.$select, 'change', this.__change );

    }

    __change () {

      this._update ();

      this._trigger ( 'change' );

    }

    /* BUTTON TAP */

    ___buttonTap () {

      if ( !Browser.is.touchDevice ) {

        /* BUTTON TAP */

        this._on ( this.$popover, Pointer.tap, this.options.selectors.button, this.__buttonTap );

      }

    }

    __buttonTap ( event ) {

      event.stopImmediatePropagation ();

      this.$popover.popover ( 'close' );

      this.set ( $(event.currentTarget).data ( this.options.datas.value ) );

    }

    /* OPTIONS */

    ___selectOptions () { //FIXME: Add support for arbitrary number of optgroups nesting levels

      let previousOptgroup;

      for ( let option of this.$options ) {

        let $option = $(option),
            $parent = $option.parent ();

        if ( $parent.is ( 'optgroup' ) ) {

          let currentOptgroup = $parent.attr ( 'label' );

          if ( currentOptgroup !== previousOptgroup ) {

            previousOptgroup = currentOptgroup;

            this.selectOptions.push ({
              prop: currentOptgroup
            });

          }

        }

        let value = $option.text ();

        if ( value ) {

          this.selectOptions.push ({
            value: $option.text (),
            prop: $option.attr ( 'value' )
          });

        }

      }

    }

    /* POPOVER */

    ___popover () {

      let html = this._template ( 'base', _.extend ( { guc: this.guc, options: this.selectOptions }, this.options.popover ) );

      this.$popover = $(html).appendTo ( this.$layout );
      this.$buttons = this.$popover.find ( this.options.selectors.button );

      this.$popover.popover ({
        positionate: {
          axis: 'y',
          strict: true
        },
        callbacks: {
          beforeopen: this.__setPopoverWidth.bind ( this ),
          open: this.__popoverOpen.bind ( this ),
          close: this.__popoverClose.bind ( this )
        }
      });

      this.$wrp.attr ( `data-${Widgets.Targeter.config.options.datas.target}`, '.' + this.guc ).popoverToggler ();

      this._updatePopover ();

    }

    __setPopoverWidth () {

      if ( this.$popover.is ( '.' + this.options.classes.affixed ) ) {

        this.$popover.css ( 'min-width', this.$wrp.outerWidth () );

      }

    }

    __popoverOpen () {

      this.___buttonTap ();

      this._trigger ( 'open' );

    }

    __popoverClose () {

      this._reset ();

      this.___change ();

      this._trigger ( 'close' );

    }

    /* UPDATE */

    _updateValueholder () {

      let value = this.$select.val ();

      if ( _.isString ( value ) && value.length ) {

        let $selectedOption = this.$options.filter ( `[value="${value}"]` ).first ();

        this.$valueholder.text ( $selectedOption.text () );

      }

    }

    _updatePopover () {

      this.$buttons.removeClass ( this.options.classes.selected );

      this.$buttons.filter ( '[data-' + this.options.datas.value + '="' + this.$select.val () + '"]' ).first ().addClass ( this.options.classes.selected );

    }

    _update () {

      this._updateValueholder ();

      if ( !Browser.is.touchDevice ) {

        this._updatePopover ();

      }

    }

    /* API */

    get () {

      return this.$select.val ();

    }

    set ( value ) {

      let $button = this.$buttons.filter ( '[data-' + this.options.datas.value + '="' + value + '"]' ).first ();

      if ( !$button.length ) return;

      this.$select.val ( value ).trigger ( 'change' );

    }

  }

  /* FACTORY */

  Factory.init ( Select, config, Widgets );

}( Svelto.$, Svelto._, Svelto, Svelto.Widgets, Svelto.Factory, Svelto.Browser, Svelto.Pointer, Svelto.Colors ));
<|endoftext|>


<commit_before>// tempart templating library

;(function(tempartParser) {
	////-----------------------------------------------------------------------------------------
	// id generator for the blocks, needs to be global because of the recusion
	tempartParser._increment = 0;
	////-----------------------------------------------------------------------------------------
	// which things are reserved words
	tempartParser.defined = ['if', 'each', 'variable', 'echo'];
	////-----------------------------------------------------------------------------------------
	// defines what commands need an {{/end}}
	tempartParser.needsEnd = ['if', 'each'];
	////-----------------------------------------------------------------------------------------
	// Precompiles the html and generates json-arrays
	tempartParser.parse = function(templateContent) {
		this._increment = 0;
		templateContent = this._escapeSpecials(templateContent);

		if(templateContent[0] == '{' && templateContent[1] == '{') {
			templateContent = templateContent.slice(2, templateContent.length);
		} else {
			templateContent = this._addEcho(templateContent);
		}
		// @TODO add check if open-blocks are the same as end-blocks
		var searches = templateContent.split('{{');
		return this._parseBlocks(searches).content;
	};
	////-----------------------------------------------------------------------------------------
	// takes an array of commands
	tempartParser._parseBlocks = function(blocks) {
		var result = [];
		var end    = null;
		while(blocks.length) {
			var block = this._parseBlock(blocks);
			if(block == 'end' || block == 'else') { // @TODO improve!
				end = block;
				break;
			} else if(block) {
				block.id = this._increment;
				result.push(block);
			}
			this._increment++;
		}
		return {content: result, end: end};
	};
	////-----------------------------------------------------------------------------------------
	// parses one command and adds new ones if needed
	tempartParser._parseBlock = function(blocks) {
		var block = blocks[0];
		var result = {};
		var end = this._getEnd(block);
		var type = block.slice(0, end).split(' ');
		if(type[0][0] == '/') {
			// @TODO add for debugging purpose a check if this was the one which was last opened
			result = 'end';
		} else if(type[0] == 'else') {
			result = 'else';
		} else if(this.defined.indexOf(type[0]) == -1) {
			result.type = 'variable';
			result.depending = [type[0]];
		} else {
			result.type    = type[0];
			type.shift();
			result.depending = type;
			if(result.type == 'echo') {
				result.content = block.slice(end + 2, block.length);
			}
		}
		blocks.shift();

		this._handleOverlength(block, blocks);
		this._handleElse(result, blocks);

		return result;
	};
	////-----------------------------------------------------------------------------------------
	// Checks if an block-has the {{else}} possibility and adds elseContains
	tempartParser._handleElse = function(result, blocks) {
		if(this.needsEnd.indexOf(result.type) != -1) {
			var contains = this._parseBlocks(blocks);
			if(contains) {
				result.contains = contains.content;
				if(contains.end == 'else') {
					result.elseContains = this._parseBlocks(blocks).content;
				} else {
					result.elseContains = [];
				}
			}
		}
	};
	////-----------------------------------------------------------------------------------------
	// Checks if an block has an html-string behind it
	tempartParser._handleOverlength = function(block, blocks) {
		var end = this._getEnd(block);
		if(block.length > end + 2 && block.slice(0, end) != 'echo') { // Handling of not-variable stuff
			blocks.unshift(this._addEcho(block.slice(end + 2, block.length)));
		}
	};
	////-----------------------------------------------------------------------------------------
	// plain html without any variable
	tempartParser._addEcho = function(echo) {
		return 'echo}}' +  echo;
	};
	////-----------------------------------------------------------------------------------------
	// returns int on which position the }} are existent
	tempartParser._getEnd = function(block) {
		return block.indexOf('}}');
	};
	////-----------------------------------------------------------------------------------------
	// escaping backslashes, single quotes, and newlines
	tempartParser._escapeSpecials = function(templateContent) {
		return templateContent.replace(/\\/g, '\\\\').replace(/\'/g, '\\\'').replace(/\n/g, '\\n').replace(/\r/g, '');
	};
}(typeof module == 'object' ? module.exports : window.tempartParser = {}));
<commit_msg>[parser] refactored it to readability<commit_after>// tempart templating library

;(function(tempartParser) {
	////-----------------------------------------------------------------------------------------
	// id generator for the blocks, needs to be global because of the recusion
	tempartParser._increment = 0;
	////-----------------------------------------------------------------------------------------
	// which things are reserved words
	tempartParser.defined = ['if', 'each', 'variable', 'echo'];
	////-----------------------------------------------------------------------------------------
	// defines what commands need an {{/end}}
	tempartParser.needsEnd = ['if', 'each'];
	////-----------------------------------------------------------------------------------------
	// Precompiles the html and generates json-arrays
	tempartParser.parse = function(templateContent) {
		this._increment = 0;
		templateContent = this._escapeSpecials(templateContent);

		if(templateContent.indexOf('{{') === 0) {
			templateContent = templateContent.slice(2, templateContent.length);
		} else {
			templateContent = this._addEcho(templateContent);
		}
		// @TODO add check if open-blocks are the same as end-blocks
		var searches = templateContent.split('{{');
		return this._parseBlocks(searches).content;
	};
	////-----------------------------------------------------------------------------------------
	// takes an array of commands
	tempartParser._parseBlocks = function(blocks) {
		var result = [];
		var end    = null;
		while(blocks.length) {
			var block = this._parseBlock(blocks);
			if(block == 'end' || block == 'else') { // @TODO improve!
				end = block;
				break;
			} else if(block) {
				block.id = this._increment;
				result.push(block);
			}
			this._increment++;
		}
		return {content: result, end: end};
	};
	////-----------------------------------------------------------------------------------------
	// parses one command and adds new ones if needed
	tempartParser._parseBlock = function(blocks) {
		var block = blocks[0];
		var result = {};
		var end = this._getEnd(block);
		var type = block.slice(0, end).split(' ');
		if(type[0][0] == '/') {
			// @TODO add for debugging purpose a check if this was the one which was last opened
			result = 'end';
		} else if(type[0] == 'else') {
			result = 'else';
		} else if(this.defined.indexOf(type[0]) == -1) {
			result.type = 'variable';
			result.depending = [type[0]];
		} else {
			result.type    = type[0];
			type.shift();
			result.depending = type;
			if(result.type == 'echo') {
				result.content = block.slice(end + 2, block.length);
			}
		}
		blocks.shift();

		this._handleOverlength(block, blocks);
		this._handleElse(result, blocks);

		return result;
	};
	////-----------------------------------------------------------------------------------------
	// Checks if an block-has the {{else}} possibility and adds elseContains
	tempartParser._handleElse = function(result, blocks) {
		if(this.needsEnd.indexOf(result.type) != -1) {
			var contains = this._parseBlocks(blocks);
			if(contains) {
				result.contains = contains.content;
				if(contains.end == 'else') {
					result.elseContains = this._parseBlocks(blocks).content;
				} else {
					result.elseContains = [];
				}
			}
		}
	};
	////-----------------------------------------------------------------------------------------
	// Checks if an block has an html-string behind it
	tempartParser._handleOverlength = function(block, blocks) {
		var end = this._getEnd(block);
		if(block.length > end + 2 && block.slice(0, end) != 'echo') { // Handling of not-variable stuff
			blocks.unshift(this._addEcho(block.slice(end + 2, block.length)));
		}
	};
	////-----------------------------------------------------------------------------------------
	// plain html without any variable
	tempartParser._addEcho = function(echo) {
		return 'echo}}' +  echo;
	};
	////-----------------------------------------------------------------------------------------
	// returns int on which position the }} are existent
	tempartParser._getEnd = function(block) {
		return block.indexOf('}}');
	};
	////-----------------------------------------------------------------------------------------
	// escaping backslashes, single quotes, and newlines
	tempartParser._escapeSpecials = function(templateContent) {
		return templateContent.replace(/\\/g, '\\\\').replace(/\'/g, '\\\'').replace(/\n/g, '\\n').replace(/\r/g, '');
	};
}(typeof module == 'object' ? module.exports : window.tempartParser = {}));
<|endoftext|>


<commit_before>var gulp = require('gulp');
var eslint = require('gulp-eslint');
var using = require('gulp-using');
var gulpif = require('gulp-if');

var args = require('yargs').argv;

gulp.task('lint', ['lint:web', 'lint:node']);

gulp.task('lint:web', () => {

  gulp.src('*.js')
    .pipe(gulpif(args.args.verbose, using({
      prefix: 'Task [lint:js] using'
    })))
    .pipe(eslint('.eslintrc'))
    .pipe(eslint.format())
    .pipe(eslint.failAfterError());
});

gulp.task('lint:node', () => {

  gulp.src(['*.js', '!./js/**.js'])
    .pipe(gulpif(args.args.verbose, using({
      prefix: 'Task [lint:js] using'
    })))
    .pipe(eslint('.eslintrc'))
    .pipe(eslint.format())
    .pipe(eslint.failAfterError());
});
<commit_msg>fix bad yargs command line variable<commit_after>var gulp = require('gulp');
var eslint = require('gulp-eslint');
var using = require('gulp-using');
var gulpif = require('gulp-if');

var args = require('yargs').argv;

gulp.task('lint', ['lint:web', 'lint:node']);

gulp.task('lint:web', () => {

  gulp.src('*.js')
    .pipe(gulpif(args.verbose, using({
      prefix: 'Task [lint:js] using'
    })))
    .pipe(eslint('.eslintrc'))
    .pipe(eslint.format())
    .pipe(eslint.failAfterError());
});

gulp.task('lint:node', () => {

  gulp.src(['*.js', '!./js/**.js'])
    .pipe(gulpif(args.verbose, using({
      prefix: 'Task [lint:js] using'
    })))
    .pipe(eslint('.eslintrc'))
    .pipe(eslint.format())
    .pipe(eslint.failAfterError());
});
<|endoftext|>


<commit_before>var fs = require('fs');
var through = require('through');

var from = process.argv[2];
var to = process.argv[3];

var readOpts = {highWaterMark: Math.pow(2,16)};
var writeOpts = {highWaterMark: Math.pow(2,16)};

var source = fs.createReadStream(__dirname + '/' + from, readOpts);
var destiny = fs.createWriteStream(__dirname + '/' + to, writeOpts);

source.pipe(through(write, end));

function write (buf) {
    destiny.write(buf.toString().replace(/,/g, ';'));
}

function end () {
    console.log('Finished parsing');
}
<commit_msg>Moved parsing into separate function, commenting<commit_after>var fs = require('fs');
var through = require('through');

// File input & output given as arguments
var from = process.argv[2];
var to = process.argv[3];

// Raise the buffer limits
var readOpts = {highWaterMark: Math.pow(2,16)};
var writeOpts = {highWaterMark: Math.pow(2,16)};

// Create file streams
var source = fs.createReadStream(__dirname + '/' + from, readOpts);
var destiny = fs.createWriteStream(__dirname + '/' + to, writeOpts);

// Pipe data from read-stream to write- and end-functions
source.pipe(through(write, end));

/**
 * Write buffer data to file
 * @param {Buffer} buf Buffer of a readable stream's data
 */
function write (buf) {
    destiny.write(parse(buf));
}

/**
 * Parse buffer data.
 * Replaces semicolons with commas.
 * @param {Buffer} buf Buffer of a readable stream's data
 * @return {String} Parsed Buffer as a String
 */
function parse (buf) {
    return buf.toString().replace(/;/g, ',');
}

/**
 * Finished writing
 */
function end () {
    console.log('Finished parsing');
}
<|endoftext|>


<commit_before>'use strict';
var request = require('request'),
  geoCoder = require('../helpers/geocoder'),
  async = require('async');

//'https://api.fda.gov/drug/event.json?search=receivedate:[20040101+TO+20150101]&count=patient.patientsex',
//'http://54.165.240.32/drug/label.json?search=effective_time:[20090601+TO+20140731]&limit=50',

var FDA_DRUG_EVENT = 'https://api.fda.gov/drug/';
var FDA_DEVICE_EVENT = 'https://api.fda.gov/device/';
var FDA_FOOD_EVENT = 'https://api.fda.gov/food/';

var FDA_END_POINTS = {
  device: 'https://api.fda.gov/device/',
  drug: 'https://api.fda.gov/drug/',
  food: 'https://api.fda.gov/food/'
};
var FDA_END_TYPES = {
  event: 'event.json',
  enforcement: 'enforcement.json',
  label: 'label.json'
};

var geoCodeFoodData = function (data, callback) {
  //var geoKeys = [];

  if (data && data instanceof Array) {
    data.map(function (item, index, array) {
      if (item.state && item.state.length > 0) {
        array[index].GeoLocation = geoCoder.geoCodeState(item.state);
      }
      else if (item.country && item.country.length > 0) {
        array[index].GeoLocation = geoCoder.geoCodeCountry(item.country);
      }
    });
    callback(data);
    /* if (geoKeys.length > 0) {
     geoCoder.geoCodeString(geoKeys, function (err, data) {
     if (data) {

     }
     });
     } else {
     callback(data);
     }*/
  }
  else {
    callback([]);
  }
};

var geoCodeDrugData = function (data, callback) {
  //var geoKeys = [];

  if (data && data instanceof Array) {
    data.map(function (item, index, array) {
      if (item.state && item.state.length > 0) {
        array[index].GeoLocation = geoCoder.geoCodeState(item.state);
      }
      else if (item.country && item.country.length > 0) {
        array[index].GeoLocation = geoCoder.geoCodeCountry(item.country);
      }
    });
    callback(data);
    /*if (geoKeys.length > 0) {
     geoCoder.geoCodeString(geoKeys, function (err, data) {
     if (data) {

     }
     });
     } else {
     callback(data);
     }*/
  }
  else {
    callback([]);
  }
};

var geoCodeDeviceData = function (data, callback) {
  //var geoKeys = [];

  if (data && data instanceof Array) {
    data.map(function (item, index, array) {
      if (item.state && item.state.length > 0) {
        array[index].GeoLocation = geoCoder.geoCodeState(item.state);
      }
      else if (item.country && item.country.length > 0) {
        array[index].GeoLocation = geoCoder.geoCodeCountry(item.country);
      }
    });
    callback(data);
    /*if (geoKeys.length > 0) {
     geoCoder.geoCodeString(geoKeys, function (err, data) {
     if (data) {

     }
     });
     } else {
     callback(data);
     }*/
  }
  else {
    callback([]);
  }
};

var formatSearchFields = function (value, fields) {
  var retSearchField = '';
  if (fields) {
    for (var idx in fields) {
      retSearchField += fields[idx].trim() + ':' + value.trim() + '+'
    }

    return retSearchField.substring(0, retSearchField.length - 1);//NOTE: Remove the last + character
  }
  else {
    return '';
  }
};

function getAggregateSplashSearchData(req, res) {
  async.parallel([
      function (callback) {
        var chosenFields = [];
        if (req.swagger.params.deviceFields.value) {
          chosenFields = req.swagger.params.deviceFields.value.split(',');
        }
        getAPIData('device', 'enforcement', req, chosenFields, function (data) {
          callback(null, {key: 'device', value: data});
        });
      },
      function (callback) {
        var chosenFields = [];
        if (req.swagger.params.foodFields.value) {
          chosenFields = req.swagger.params.foodFields.value.split(',');
        }
        getAPIData('food', 'enforcement', req, chosenFields, function (data) {
          geoCodeFoodData(data, function (data) {
            callback(null, {key: 'food', value: data});
          });
        });
      },
      function (callback) {
        var chosenFields = [];
        if (req.swagger.params.drugFields.value) {
          chosenFields = req.swagger.params.drugFields.value.split(',');
        }
        getAPIData('drug', 'enforcement', req, chosenFields, function (data) {
          geoCodeDrugData(data, function (data) {
            callback(null, {key: 'drug', value: data});
          });
        });

      }
    ],
    function (err, data) {
      var returnData = {drug: [], device: [], food: []};
      for (var idx in data) {
        if (data[idx].value instanceof  Array) {
          returnData[data[idx].key] = data[idx].value;
        }
      }
      var statusMessage = '';
      statusMessage += returnData.drug.length > 0 ? returnData.drug.length + ' drug records, ' : 'No drug data, ';
      statusMessage += returnData.food.length > 0 ? returnData.food.length + ' food records, ' : 'No food data, ';
      statusMessage += returnData.device.length > 0 ? returnData.device.length + ' device records, ' : 'No device data ';
      returnData.status = statusMessage;
      res.json(returnData);
    }
  );
}

function getRangeCountData(req, callback) {
  var fdaUrl = FDA_DRUG_EVENT + 'event.json?search=receivedate:[' + req.swagger.params.start.value + '+TO+' + req.swagger.params.end.value + ']&count=' + req.swagger.params.field.value;
  getDataFromFdaApi(fdaUrl, function (data) {
    callback(data);
  });
}

function getEventSearchData(req, callback) {
  var limit = req.swagger.params.limit.value || 100;
  var start = req.swagger.params.skip.value || 0;
  var fdaUrl = FDA_DRUG_EVENT + 'event.json?search=' + req.swagger.params.query.value + '&limit=' + limit + '&skip=' + start;

  getDataFromFdaApi(fdaUrl, function (data) {
    callback(data);
  });
}

var getAPIData = function (endPointBase, typeOfEngPoint, req, fields, callback) {
  var limit = 100, start = 0, search;
  if (req.swagger.params.limit) {
    limit = req.swagger.params.limit.value || 100;
  }
  if (req.swagger.params.skip) {
    start = req.swagger.params.skip.value || 0;
  }
  if (req.swagger.params.query) {
    search = req.swagger.params.query.value;
  }
  if (fields.length > 0) {
    search = formatSearchFields(req.swagger.params.query.value, fields);
  }
  var fdaUrl = FDA_END_POINTS[endPointBase] + FDA_END_TYPES[typeOfEngPoint] + '?search=' + search;

  if (limit) {
    fdaUrl += '&limit=' + limit;
  }
  if (start) {
    fdaUrl += '&skip=' + start;
  }
  getDataFromFdaApi(fdaUrl, callback);
};

var getAPIRangeData = function (endPointBase, typeOfEngPoint, datefield, req, callback) {
  var fdaUrl = FDA_END_POINTS[endPointBase] + FDA_END_TYPES[typeOfEngPoint] + "?search=" + datefield
    + ':[' + req.swagger.params.start.value + '+TO+' + req.swagger.params.end.value + ']&count=' + req.swagger.params.field.value;
  getDataFromFdaApi(fdaUrl, callback);
};

function getDataFromFdaApi(fdaUrl, callback) {
  request.get({
      url: encodeURI(fdaUrl),
      json: true
    },
    function (error, response, body) {
      if (!error && (response.statusCode == 200 || 404)) {
        callback(response.statusCode == 404 ? body : body.results);
      } else {
        callback({error: 'Request Failed'});
      }
    }
  );
}

module.exports = {
  aggregateSplashSearch: function (req, res) {
    getAggregateSplashSearchData(req, res)
  },

  drugEventSearch: function (req, res) {
    var chosenFields = [];
    if (req.swagger.params.fields.value) {
      chosenFields = req.swagger.params.fields.value.split(',');
    }
    getAPIData('drug', 'enforcement', req, chosenFields, function (data) {
      geoCodeDrugData(data, function (data) {
        res.json(data);
      });
    });
  },

  drugEventRangeCount: function (req, res) {
    getRangeCountData(req, function (data) {
      res.json(data);
    });
  },

  deviceEventSearch: function (req, res) {
    var chosenFields = [];
    if (req.swagger.params.fields.value) {
      chosenFields = req.swagger.params.deviceFields.value.split(',');
    }
    getAPIData('device', 'enforcement', req, chosenFields, function (data) {
      geoCodeDeviceData(data, function (processedData) {
        res.json(processedData);
      });
    });
  },

  deviceEventRangeCount: function (req, res) {
    getAPIRangeData('device', 'enforcement', 'report_date', req, function (data) {
      res.json(data);
    });
  },

  foodEventSearch: function (req, res) {
    var chosenFields = [];
    if (req.swagger.params.fields.value) {
      chosenFields = req.swagger.params.fields.value.split(',');
    }
    getAPIData('food', 'enforcement', req, chosenFields, function (data) {
      geoCodeFoodData(data, function (data) {
        res.json(data);
      });
    });
  },

  foodEventRangeCount: function (req, res) {
    getAPIRangeData('food', 'enforcement', 'recall_initiation_date', req, function (data) {
      res.json(data);
    });
  },

  tests: {
    getRangeCountData: getRangeCountData,
    getEventSearchData: getEventSearchData,
    FDA_END_POINTS: FDA_END_POINTS,
    FDA_END_TYPES: FDA_END_TYPES,
    getAPIData: getAPIData,
    getAPIRangeData: getAPIRangeData
  }
};<commit_msg>Adding another filter if and function check<commit_after>'use strict';
var request = require('request'),
  geoCoder = require('../helpers/geocoder'),
  async = require('async');

//'https://api.fda.gov/drug/event.json?search=receivedate:[20040101+TO+20150101]&count=patient.patientsex',
//'http://54.165.240.32/drug/label.json?search=effective_time:[20090601+TO+20140731]&limit=50',

var FDA_DRUG_EVENT = 'https://api.fda.gov/drug/';
var FDA_DEVICE_EVENT = 'https://api.fda.gov/device/';
var FDA_FOOD_EVENT = 'https://api.fda.gov/food/';

var FDA_END_POINTS = {
  device: 'https://api.fda.gov/device/',
  drug: 'https://api.fda.gov/drug/',
  food: 'https://api.fda.gov/food/'
};
var FDA_END_TYPES = {
  event: 'event.json',
  enforcement: 'enforcement.json',
  label: 'label.json'
};

var geoCodeFoodData = function (data, callback) {
  //var geoKeys = [];

  if (data && data instanceof Array) {
    data.map(function (item, index, array) {
      if (item.state && item.state.length > 0) {
        array[index].GeoLocation = geoCoder.geoCodeState(item.state);
      }
      else if (item.country && item.country.length > 0) {
        array[index].GeoLocation = geoCoder.geoCodeCountry(item.country);
      }
    });
    callback(data);
    /* if (geoKeys.length > 0) {
     geoCoder.geoCodeString(geoKeys, function (err, data) {
     if (data) {

     }
     });
     } else {
     callback(data);
     }*/
  }
  else {
    callback([]);
  }
};

var geoCodeDrugData = function (data, callback) {
  //var geoKeys = [];

  if (data && data instanceof Array) {
    data.map(function (item, index, array) {
      if (item.state && item.state.length > 0) {
        array[index].GeoLocation = geoCoder.geoCodeState(item.state);
      }
      else if (item.country && item.country.length > 0) {
        array[index].GeoLocation = geoCoder.geoCodeCountry(item.country);
      }
    });
    callback(data);
    /*if (geoKeys.length > 0) {
     geoCoder.geoCodeString(geoKeys, function (err, data) {
     if (data) {

     }
     });
     } else {
     callback(data);
     }*/
  }
  else {
    callback([]);
  }
};

var geoCodeDeviceData = function (data, callback) {
  //var geoKeys = [];

  if (data && data instanceof Array) {
    data.map(function (item, index, array) {
      if (item.state && item.state.length > 0) {
        array[index].GeoLocation = geoCoder.geoCodeState(item.state);
      }
      else if (item.country && item.country.length > 0) {
        array[index].GeoLocation = geoCoder.geoCodeCountry(item.country);
      }
    });
    callback(data);
    /*if (geoKeys.length > 0) {
     geoCoder.geoCodeString(geoKeys, function (err, data) {
     if (data) {

     }
     });
     } else {
     callback(data);
     }*/
  }
  else {
    callback([]);
  }
};

var formatSearchFields = function (value, fields) {
  var retSearchField = '';
  if (fields && fields instanceof Array) {
    for (var idx in fields) {
      if (typeof fields[idx] === 'string' || fields[idx] instanceof String) {
        retSearchField += fields[idx].trim() + ':' + value.trim() + '+';
      }
    }
    return retSearchField.substring(0, retSearchField.length - 1);//NOTE: Remove the last + character
  }
  else {
    return '';
  }
};

function getAggregateSplashSearchData(req, res) {
  async.parallel([
      function (callback) {
        var chosenFields = [];
        if (req.swagger.params.deviceFields.value) {
          chosenFields = req.swagger.params.deviceFields.value.split(',');
        }
        getAPIData('device', 'enforcement', req, chosenFields, function (data) {
          callback(null, {key: 'device', value: data});
        });
      },
      function (callback) {
        var chosenFields = [];
        if (req.swagger.params.foodFields.value) {
          chosenFields = req.swagger.params.foodFields.value.split(',');
        }
        getAPIData('food', 'enforcement', req, chosenFields, function (data) {
          geoCodeFoodData(data, function (data) {
            callback(null, {key: 'food', value: data});
          });
        });
      },
      function (callback) {
        var chosenFields = [];
        if (req.swagger.params.drugFields.value) {
          chosenFields = req.swagger.params.drugFields.value.split(',');
        }
        getAPIData('drug', 'enforcement', req, chosenFields, function (data) {
          geoCodeDrugData(data, function (data) {
            callback(null, {key: 'drug', value: data});
          });
        });

      }
    ],
    function (err, data) {
      var returnData = {drug: [], device: [], food: []};
      for (var idx in data) {
        if (data[idx].value instanceof  Array) {
          returnData[data[idx].key] = data[idx].value;
        }
      }
      var statusMessage = '';
      statusMessage += returnData.drug.length > 0 ? returnData.drug.length + ' drug records, ' : 'No drug data, ';
      statusMessage += returnData.food.length > 0 ? returnData.food.length + ' food records, ' : 'No food data, ';
      statusMessage += returnData.device.length > 0 ? returnData.device.length + ' device records, ' : 'No device data ';
      returnData.status = statusMessage;
      res.json(returnData);
    }
  );
}

function getRangeCountData(req, callback) {
  var fdaUrl = FDA_DRUG_EVENT + 'event.json?search=receivedate:[' + req.swagger.params.start.value + '+TO+' + req.swagger.params.end.value + ']&count=' + req.swagger.params.field.value;
  getDataFromFdaApi(fdaUrl, function (data) {
    callback(data);
  });
}

function getEventSearchData(req, callback) {
  var limit = req.swagger.params.limit.value || 100;
  var start = req.swagger.params.skip.value || 0;
  var fdaUrl = FDA_DRUG_EVENT + 'event.json?search=' + req.swagger.params.query.value + '&limit=' + limit + '&skip=' + start;

  getDataFromFdaApi(fdaUrl, function (data) {
    callback(data);
  });
}

var getAPIData = function (endPointBase, typeOfEngPoint, req, fields, callback) {
  var limit = 100, start = 0, search;
  if (req.swagger.params.limit) {
    limit = req.swagger.params.limit.value || 100;
  }
  if (req.swagger.params.skip) {
    start = req.swagger.params.skip.value || 0;
  }
  if (req.swagger.params.query) {
    search = req.swagger.params.query.value;
  }
  if (fields.length > 0) {
    search = formatSearchFields(req.swagger.params.query.value, fields);
  }
  var fdaUrl = FDA_END_POINTS[endPointBase] + FDA_END_TYPES[typeOfEngPoint] + '?search=' + search;

  if (limit) {
    fdaUrl += '&limit=' + limit;
  }
  if (start) {
    fdaUrl += '&skip=' + start;
  }
  getDataFromFdaApi(fdaUrl, callback);
};

var getAPIRangeData = function (endPointBase, typeOfEngPoint, datefield, req, callback) {
  var fdaUrl = FDA_END_POINTS[endPointBase] + FDA_END_TYPES[typeOfEngPoint] + "?search=" + datefield
    + ':[' + req.swagger.params.start.value + '+TO+' + req.swagger.params.end.value + ']&count=' + req.swagger.params.field.value;
  getDataFromFdaApi(fdaUrl, callback);
};

function getDataFromFdaApi(fdaUrl, callback) {
  request.get({
      url: encodeURI(fdaUrl),
      json: true
    },
    function (error, response, body) {
      if (!error && (response.statusCode == 200 || 404)) {
        callback(response.statusCode == 404 ? body : body.results);
      } else {
        callback({error: 'Request Failed'});
      }
    }
  );
}

module.exports = {
  aggregateSplashSearch: function (req, res) {
    getAggregateSplashSearchData(req, res)
  },

  drugEventSearch: function (req, res) {
    var chosenFields = [];
    if (req.swagger.params.fields.value) {
      chosenFields = req.swagger.params.fields.value.split(',');
    }
    getAPIData('drug', 'enforcement', req, chosenFields, function (data) {
      geoCodeDrugData(data, function (data) {
        res.json(data);
      });
    });
  },

  drugEventRangeCount: function (req, res) {
    getRangeCountData(req, function (data) {
      res.json(data);
    });
  },

  deviceEventSearch: function (req, res) {
    var chosenFields = [];
    if (req.swagger.params.fields.value) {
      chosenFields = req.swagger.params.deviceFields.value.split(',');
    }
    getAPIData('device', 'enforcement', req, chosenFields, function (data) {
      geoCodeDeviceData(data, function (processedData) {
        res.json(processedData);
      });
    });
  },

  deviceEventRangeCount: function (req, res) {
    getAPIRangeData('device', 'enforcement', 'report_date', req, function (data) {
      res.json(data);
    });
  },

  foodEventSearch: function (req, res) {
    var chosenFields = [];
    if (req.swagger.params.fields.value) {
      chosenFields = req.swagger.params.fields.value.split(',');
    }
    getAPIData('food', 'enforcement', req, chosenFields, function (data) {
      geoCodeFoodData(data, function (data) {
        res.json(data);
      });
    });
  },

  foodEventRangeCount: function (req, res) {
    getAPIRangeData('food', 'enforcement', 'recall_initiation_date', req, function (data) {
      res.json(data);
    });
  },

  tests: {
    getRangeCountData: getRangeCountData,
    getEventSearchData: getEventSearchData,
    FDA_END_POINTS: FDA_END_POINTS,
    FDA_END_TYPES: FDA_END_TYPES,
    getAPIData: getAPIData,
    getAPIRangeData: getAPIRangeData
  }
};<|endoftext|>


<commit_before>const gulp = require('gulp');
const mochaPhantomJS = require('gulp-mocha-phantomjs');
const config = require('../config');

module.exports = function test() {
  return gulp.src('test/runner.html', {
    read: false,
  })
  .pipe(mochaPhantomJS({
    phantomjs: {
      useColors: true,
    },
  }))

  // https://github.com/gulpjs/gulp/issues/259#issuecomment-61976830
  .on('error', (err) => {
    if (config.watch) {
      console.error(err.message);
      this.emit('end');
    }
  });
};
<commit_msg>Fix test task crash.<commit_after>const gulp = require('gulp');
const mochaPhantomJS = require('gulp-mocha-phantomjs');
const config = require('../config');

module.exports = function test() {
  return gulp.src('test/runner.html', {
    read: false,
  })
  .pipe(mochaPhantomJS({
    phantomjs: {
      useColors: true,
    },
  }))

  // https://github.com/gulpjs/gulp/issues/259#issuecomment-61976830
  .on('error', function onerror(err) {
    if (config.watch) {
      console.error(err.message);
      this.emit('end');
    }
  });
};
<|endoftext|>


<commit_before>// @flow

export type ElementType = "link" | "clickable" | "frame";

type ElementData = {|
  type: ElementType,
|};

export type Box = {|
  x: number,
  y: number,
  width: number,
  height: number,
|};

export type HintMeasurements = {|
  x: number,
  y: number,
  area: number,
|};

export default class ElementManager {
  maxTrackedElements: number;
  elements: Map<HTMLElement, ElementData>;
  visibleElements: Set<HTMLElement>;
  intersectionObserver: IntersectionObserver;
  mutationObserver: MutationObserver;
  bailed: boolean;

  constructor({ maxTrackedElements }: {| maxTrackedElements: number |}) {
    this.maxTrackedElements = maxTrackedElements;

    this.elements = new Map();
    this.visibleElements = new Set();

    this.intersectionObserver = new IntersectionObserver(
      this.onIntersection.bind(this),
      {}
    );

    this.mutationObserver = new MutationObserver(this.onMutation.bind(this));
    this.bailed = false;
  }

  start() {
    const { documentElement } = document;
    if (documentElement != null) {
      this.addElements(documentElement);
      this.mutationObserver.observe(documentElement, {
        childList: true,
        subtree: true,
      });
    }
  }

  stop() {
    this.intersectionObserver.disconnect();
    this.mutationObserver.disconnect();
    this.elements.clear();
    this.visibleElements.clear();
  }

  // Stop tracking everything except frames (up to `maxTrackedElements` of them).
  bail() {
    if (this.bailed) {
      return;
    }

    this.intersectionObserver.disconnect();
    this.elements.clear();
    this.visibleElements.clear();
    this.bailed = true;

    const { documentElement } = document;
    if (documentElement != null) {
      const frames = document.querySelectorAll("iframe, frame");
      for (const frame of frames) {
        this.addElements(frame);
      }
    }

    console.log("bailed", this);
  }

  onIntersection(entries: Array<IntersectionObserverEntry>) {
    for (const entry of entries) {
      if (entry.isIntersecting) {
        this.visibleElements.add(entry.target);
      } else {
        this.visibleElements.delete(entry.target);
      }
    }
  }

  onMutation(records: Array<MutationRecord>) {
    for (const record of records) {
      for (const node of record.addedNodes) {
        if (node instanceof HTMLElement) {
          this.addElements(node);
          if (this.bailed) {
            return;
          }
        }
      }

      for (const node of record.removedNodes) {
        if (node instanceof HTMLElement) {
          this.removeElements(node);
        }
      }
    }
  }

  addElements(parent: HTMLElement) {
    let { size } = this.elements;
    const elements = [parent, ...parent.querySelectorAll("*")];
    for (const element of elements) {
      const data = getElementData(element);
      if (data != null && (!this.bailed || data.type === "frame")) {
        this.elements.set(element, data);
        this.intersectionObserver.observe(element);
        size++;
        if (size > this.maxTrackedElements) {
          this.bail();
          break;
        }
      }
    }
  }

  removeElements(parent: HTMLElement) {
    const elements = [parent, ...parent.querySelectorAll("*")];
    for (const element of elements) {
      if (this.elements.has(element)) {
        this.elements.delete(element);
        this.intersectionObserver.unobserve(element);
      }
    }
  }

  getVisibleElements(
    types: Set<ElementType>,
    viewports: Array<Box>
  ): Array<{|
    element: HTMLElement,
    data: ElementData,
    measurements: HintMeasurements,
  |}> {
    const candidates = this.bailed
      ? document.documentElement == null
        ? []
        : document.documentElement.querySelectorAll("*")
      : this.visibleElements;

    const range = document.createRange();

    return Array.from(candidates, element => {
      const data = this.bailed
        ? getElementData(element)
        : this.elements.get(element);

      if (data == null) {
        return undefined;
      }

      if (!types.has(data.type)) {
        return undefined;
      }

      const measurements = getMeasurements(element, viewports, range);

      if (measurements == null) {
        return undefined;
      }

      return {
        element,
        data,
        measurements,
      };
    }).filter(Boolean);
  }

  getVisibleFrames(): Array<HTMLIFrameElement | HTMLFrameElement> {
    return Array.from(
      this.visibleElements,
      element =>
        (element instanceof HTMLIFrameElement ||
          element instanceof HTMLFrameElement) &&
        // Needed on reddit.com. There's a Google Ads iframe without `src` where
        // `contentWindow` is null.
        element.contentWindow != null
          ? element
          : undefined
    ).filter(Boolean);
  }
}

function getMeasurements(
  element: HTMLElement,
  viewports: Array<Box>,
  // The `range` is passed in since it is faster to re-use the same one than
  // creating a new one for every element candidate.
  range: Range
): ?HintMeasurements {
  const rect = element.getBoundingClientRect();
  const visibleBox = getVisibleBox(rect, viewports);

  if (visibleBox == null) {
    return undefined;
  }

  // Try to place the hint just before the first letter inside `element`, if
  // any. If the first letter is off-screen, don’t bother with any fancy
  // placement and just place the hint in the middle of `visibleBox`. The first
  // non-empty text node is assumed to come first. That’s not necessarily true
  // due to CSS, but YAGNI until that’s found in the wild. One would think that
  // `range.selectNodeContents(element)` would do essentially the same thing
  // here, but it takes padding and such of child elements into account. Also,
  // it would count leading visible whitespace as the first character.
  let textRect = undefined;
  const first = getFirstNonEmptyTextNode(element);
  if (first != null) {
    range.setStart(first.node, first.index);
    range.setEnd(first.node, first.index + 1);
    // Use `.getClientRects()` rather than `.getBoundingClientRect()` in case
    // the text is line-wrapped.
    textRect = range.getClientRects()[0];
  }
  const visibleTextBox =
    textRect == null ? undefined : getVisibleBox(textRect, viewports);

  // The box used to choose the position of the hint.
  const pointBox = visibleTextBox == null ? visibleBox : visibleTextBox;

  const [offsetX, offsetY] = viewports.reduceRight(
    ([x, y], viewport) => [x + viewport.x, y + viewport.y],
    [0, 0]
  );

  const { x } = pointBox;
  const y = pointBox.y + pointBox.height / 2;

  // Check that the element isn’t covered. A little bit expensive, but totally
  // worth it since it makes link hints in fixed menus so much easier find.
  // Even if some other part than `(x, y)` is visible, don’t bother if `(x, y)`
  // isn’t visible. Too much work for too little gain. Finally, add 1px to `x`.
  // It feels safer to test 1px into the element rather than at the very edge.
  // `getVisibleBox` guarantees the box to be at least 1px wide. Remove
  // `offsetX` and `offsetY` to turn `x` and `y` back to the coordinate system
  // of the current frame.
  const elementAtPoint = document.elementFromPoint(
    x - offsetX + 1,
    y - offsetY
  );

  // `.contains` also checks `element === elementAtPoint`.
  if (!element.contains(elementAtPoint)) {
    return undefined;
  }

  return {
    // The coordinates at which to place the hint.
    x,
    y,
    // It’s easy to think that one could optimize by calculating the area from
    // `pointBox` and potentially skip `element.getBoundingClientRect()` for
    // most elements, but remember that `pointBox` most likely just refers to
    // (part of) one text node of the element, not the entire visible area of
    // the element (as `visibleBox` does).
    area: visibleBox.width * visibleBox.height,
  };
}

// Turn a `ClientRect` into a `Box` using the coordinates of the topmost
// viewport. Only the part of the `ClientRect` visible through all viewports end
// up in the `Box`.
function getVisibleBox(passedRect: ClientRect, viewports: Array<Box>): ?Box {
  // No shortcuts (such as summing up viewport x:s and y:s) can be taken here,
  // since each viewport (frame) clips the visible area. We have to loop them
  // all through.
  const visibleRect = viewports.reduceRight(
    (rect, viewport) => ({
      left: viewport.x + Math.max(rect.left, 0),
      right: viewport.x + Math.min(rect.right, viewport.width),
      top: viewport.y + Math.max(rect.top, 0),
      bottom: viewport.y + Math.min(rect.bottom, viewport.height),
    }),
    passedRect
  );

  const width = visibleRect.right - visibleRect.left;
  const height = visibleRect.bottom - visibleRect.top;

  // If `visibleRect` has a non-sensical width or height it means it is not
  // visible within `viewports`.
  return width <= 0 || height <= 0
    ? undefined
    : {
        x: visibleRect.left,
        y: visibleRect.top,
        width,
        height,
      };
}

function getElementData(element: HTMLElement): ?{| type: ElementType |} {
  const type = getElementType(element);
  return type == null ? undefined : { type };
}

function getElementType(element: HTMLElement): ?ElementType {
  switch (element.nodeName) {
    case "A":
      return "link";
    case "BUTTON":
    case "LABEL":
    case "SELECT":
    case "SUMMARY":
    case "TEXTAREA":
      return "clickable";
    case "INPUT":
      // $FlowIgnore: Flow can't know, but `.type` _does_ exist here.
      return element.type === "hidden" ? undefined : "clickable";
    case "FRAME":
    case "IFRAME":
      return "frame";
    default:
      return undefined;
  }
}

function getFirstNonEmptyTextNode(
  element: HTMLElement
): ?{| node: Text, index: number |} {
  for (const node of element.childNodes) {
    if (node instanceof Text) {
      const index = node.data.search(/\S/);
      if (index >= 0) {
        return { node, index };
      }
    } else if (node instanceof HTMLElement) {
      const result = getFirstNonEmptyTextNode(node);
      if (result != null) {
        return result;
      }
    }
  }
  return undefined;
}
<commit_msg>Get element area more accurately<commit_after>// @flow

export type ElementType = "link" | "clickable" | "frame";

type ElementData = {|
  type: ElementType,
|};

export type Box = {|
  x: number,
  y: number,
  width: number,
  height: number,
|};

export type HintMeasurements = {|
  x: number,
  y: number,
  area: number,
|};

export default class ElementManager {
  maxTrackedElements: number;
  elements: Map<HTMLElement, ElementData>;
  visibleElements: Set<HTMLElement>;
  intersectionObserver: IntersectionObserver;
  mutationObserver: MutationObserver;
  bailed: boolean;

  constructor({ maxTrackedElements }: {| maxTrackedElements: number |}) {
    this.maxTrackedElements = maxTrackedElements;

    this.elements = new Map();
    this.visibleElements = new Set();

    this.intersectionObserver = new IntersectionObserver(
      this.onIntersection.bind(this),
      {}
    );

    this.mutationObserver = new MutationObserver(this.onMutation.bind(this));
    this.bailed = false;
  }

  start() {
    const { documentElement } = document;
    if (documentElement != null) {
      this.addElements(documentElement);
      this.mutationObserver.observe(documentElement, {
        childList: true,
        subtree: true,
      });
    }
  }

  stop() {
    this.intersectionObserver.disconnect();
    this.mutationObserver.disconnect();
    this.elements.clear();
    this.visibleElements.clear();
  }

  // Stop tracking everything except frames (up to `maxTrackedElements` of them).
  bail() {
    if (this.bailed) {
      return;
    }

    this.intersectionObserver.disconnect();
    this.elements.clear();
    this.visibleElements.clear();
    this.bailed = true;

    const { documentElement } = document;
    if (documentElement != null) {
      const frames = document.querySelectorAll("iframe, frame");
      for (const frame of frames) {
        this.addElements(frame);
      }
    }

    console.log("bailed", this);
  }

  onIntersection(entries: Array<IntersectionObserverEntry>) {
    for (const entry of entries) {
      if (entry.isIntersecting) {
        this.visibleElements.add(entry.target);
      } else {
        this.visibleElements.delete(entry.target);
      }
    }
  }

  onMutation(records: Array<MutationRecord>) {
    for (const record of records) {
      for (const node of record.addedNodes) {
        if (node instanceof HTMLElement) {
          this.addElements(node);
          if (this.bailed) {
            return;
          }
        }
      }

      for (const node of record.removedNodes) {
        if (node instanceof HTMLElement) {
          this.removeElements(node);
        }
      }
    }
  }

  addElements(parent: HTMLElement) {
    let { size } = this.elements;
    const elements = [parent, ...parent.querySelectorAll("*")];
    for (const element of elements) {
      const data = getElementData(element);
      if (data != null && (!this.bailed || data.type === "frame")) {
        this.elements.set(element, data);
        this.intersectionObserver.observe(element);
        size++;
        if (size > this.maxTrackedElements) {
          this.bail();
          break;
        }
      }
    }
  }

  removeElements(parent: HTMLElement) {
    const elements = [parent, ...parent.querySelectorAll("*")];
    for (const element of elements) {
      if (this.elements.has(element)) {
        this.elements.delete(element);
        this.intersectionObserver.unobserve(element);
      }
    }
  }

  getVisibleElements(
    types: Set<ElementType>,
    viewports: Array<Box>
  ): Array<{|
    element: HTMLElement,
    data: ElementData,
    measurements: HintMeasurements,
  |}> {
    const candidates = this.bailed
      ? document.documentElement == null
        ? []
        : document.documentElement.querySelectorAll("*")
      : this.visibleElements;

    const range = document.createRange();

    return Array.from(candidates, element => {
      const data = this.bailed
        ? getElementData(element)
        : this.elements.get(element);

      if (data == null) {
        return undefined;
      }

      if (!types.has(data.type)) {
        return undefined;
      }

      const measurements = getMeasurements(element, viewports, range);

      if (measurements == null) {
        return undefined;
      }

      return {
        element,
        data,
        measurements,
      };
    }).filter(Boolean);
  }

  getVisibleFrames(): Array<HTMLIFrameElement | HTMLFrameElement> {
    return Array.from(
      this.visibleElements,
      element =>
        (element instanceof HTMLIFrameElement ||
          element instanceof HTMLFrameElement) &&
        // Needed on reddit.com. There's a Google Ads iframe without `src` where
        // `contentWindow` is null.
        element.contentWindow != null
          ? element
          : undefined
    ).filter(Boolean);
  }
}

function getMeasurements(
  element: HTMLElement,
  viewports: Array<Box>,
  // The `range` is passed in since it is faster to re-use the same one than
  // creating a new one for every element candidate.
  range: Range
): ?HintMeasurements {
  const rect = element.getBoundingClientRect();
  const visibleBox = getVisibleBox(rect, viewports);

  if (visibleBox == null) {
    return undefined;
  }

  // Try to place the hint just before the first letter inside `element`, if
  // any. If the first letter is off-screen, don’t bother with any fancy
  // placement and just place the hint in the middle of `visibleBox`. The first
  // non-empty text node is assumed to come first. That’s not necessarily true
  // due to CSS, but YAGNI until that’s found in the wild. One would think that
  // `range.selectNodeContents(element)` would do essentially the same thing
  // here, but it takes padding and such of child elements into account. Also,
  // it would count leading visible whitespace as the first character.
  let textRect = undefined;
  const first = getFirstNonEmptyTextNode(element);
  if (first != null) {
    range.setStart(first.node, first.index);
    range.setEnd(first.node, first.index + 1);
    // Use `.getClientRects()` rather than `.getBoundingClientRect()` in case
    // the text is line-wrapped.
    textRect = range.getClientRects()[0];
  }
  const visibleTextBox =
    textRect == null ? undefined : getVisibleBox(textRect, viewports);

  // The box used to choose the position of the hint.
  const pointBox = visibleTextBox == null ? visibleBox : visibleTextBox;

  const [offsetX, offsetY] = viewports.reduceRight(
    ([x, y], viewport) => [x + viewport.x, y + viewport.y],
    [0, 0]
  );

  const { x } = pointBox;
  const y = pointBox.y + pointBox.height / 2;

  // Check that the element isn’t covered. A little bit expensive, but totally
  // worth it since it makes link hints in fixed menus so much easier find.
  // Even if some other part than `(x, y)` is visible, don’t bother if `(x, y)`
  // isn’t visible. Too much work for too little gain. Finally, add 1px to `x`.
  // It feels safer to test 1px into the element rather than at the very edge.
  // `getVisibleBox` guarantees the box to be at least 1px wide. Remove
  // `offsetX` and `offsetY` to turn `x` and `y` back to the coordinate system
  // of the current frame.
  const elementAtPoint = document.elementFromPoint(
    x - offsetX + 1,
    y - offsetY
  );

  // `.contains` also checks `element === elementAtPoint`.
  if (!element.contains(elementAtPoint)) {
    return undefined;
  }

  return {
    // The coordinates at which to place the hint.
    x,
    y,
    // It’s easy to think that one could optimize by calculating the area from
    // `pointBox` and potentially skip `element.getBoundingClientRect()` for
    // most elements, but remember that `pointBox` most likely just refers to
    // (part of) one text node of the element, not the entire visible area of
    // the element (as `visibleBox` does).
    area: getArea(element, visibleBox),
  };
}

// Turn a `ClientRect` into a `Box` using the coordinates of the topmost
// viewport. Only the part of the `ClientRect` visible through all viewports end
// up in the `Box`.
function getVisibleBox(passedRect: ClientRect, viewports: Array<Box>): ?Box {
  // No shortcuts (such as summing up viewport x:s and y:s) can be taken here,
  // since each viewport (frame) clips the visible area. We have to loop them
  // all through.
  const visibleRect = viewports.reduceRight(
    (rect, viewport) => ({
      left: viewport.x + Math.max(rect.left, 0),
      right: viewport.x + Math.min(rect.right, viewport.width),
      top: viewport.y + Math.max(rect.top, 0),
      bottom: viewport.y + Math.min(rect.bottom, viewport.height),
    }),
    passedRect
  );

  const width = visibleRect.right - visibleRect.left;
  const height = visibleRect.bottom - visibleRect.top;

  // If `visibleRect` has a non-sensical width or height it means it is not
  // visible within `viewports`.
  return width <= 0 || height <= 0
    ? undefined
    : {
        x: visibleRect.left,
        y: visibleRect.top,
        width,
        height,
      };
}

function getElementData(element: HTMLElement): ?{| type: ElementType |} {
  const type = getElementType(element);
  return type == null ? undefined : { type };
}

function getElementType(element: HTMLElement): ?ElementType {
  switch (element.nodeName) {
    case "A":
      return "link";
    case "BUTTON":
    case "LABEL":
    case "SELECT":
    case "SUMMARY":
    case "TEXTAREA":
      return "clickable";
    case "INPUT":
      // $FlowIgnore: Flow can't know, but `.type` _does_ exist here.
      return element.type === "hidden" ? undefined : "clickable";
    case "FRAME":
    case "IFRAME":
      return "frame";
    default:
      return undefined;
  }
}

function getFirstNonEmptyTextNode(
  element: HTMLElement
): ?{| node: Text, index: number |} {
  for (const node of element.childNodes) {
    if (node instanceof Text) {
      const index = node.data.search(/\S/);
      if (index >= 0) {
        return { node, index };
      }
    } else if (node instanceof HTMLElement) {
      const result = getFirstNonEmptyTextNode(node);
      if (result != null) {
        return result;
      }
    }
  }
  return undefined;
}

function getArea(element: HTMLElement, visibleBox: Box): number {
  const rects = element.getClientRects();
  let area = 0;
  for (const rect of rects) {
    const visible = {
      left: Math.max(rect.left, visibleBox.x),
      right: Math.min(rect.right, visibleBox.x + visibleBox.width),
      top: Math.max(rect.top, visibleBox.y),
      bottom: Math.min(rect.bottom, visibleBox.y + visibleBox.height),
    };
    const width = visible.right - visible.left;
    const height = visible.bottom - visible.top;
    if (width > 0 && height > 0) {
      area += width * height;
    }
  }
  return area;
}
<|endoftext|>