<commit_before>/* eslint-disable max-lines, max-statements, no-lonely-if */
/* global google */

// LocationMarkers is a marker manager.

var markerStore = tresdb.stores.markers;
var account = tresdb.stores.account;
var getBoundsDiagonal = require('./lib/getBoundsDiagonal');
var rawEventToMarkerLocation = require('./lib/rawEventToMarkerLocation');
var labels = require('./lib/labels');
var chooseIcon = require('./chooseIcon');
var VisitedManager = require('./VisitedManager');
var emitter = require('component-emitter');

module.exports = function (map) {

  // Init
  var self = this;
  emitter(self);

  // Location markers on the map. A mapping from id to google.maps.Marker
  var _markers = {};

  // Does the map load markers from the locations and display them on the map.
  // Will be set when client is ready to load markers (logged in)
  var _loaderListener = null;

  // True if map is ready for use. Will be switched to true at the first
  // 'idle' event. See addListenerOnce('idle', ...) below.
  // We need to track this for startLoadingMarkers: if map is ready
  // then load the markers immediately when called, otherwise wait for idle.
  var _mapReady = false;

  // Array of ids of locations that the user has visited.
  // Fetch them as soon as possible.
  var _visitedIds = new VisitedManager();

  // Private methods

  var _chooseIcon = function (mloc) {
    var zoomLevel = map.getZoom();
    return chooseIcon(mloc, zoomLevel, _visitedIds);
  };

  var _addMarker = function (loc) {
    // Create marker and add it to the map.
    //
    // Parameters:
    //   loc
    //     MarkerLocation
    // Return
    //   the created marker
    var lng, lat, m;

    lng = loc.geom.coordinates[0];
    lat = loc.geom.coordinates[1];

    m = new google.maps.Marker({
      position: new google.maps.LatLng(lat, lng),
      icon: _chooseIcon(loc),
    });

    // Store the MarkerLocation for _id, name, and layer info.
    m.set('location', loc);

    // Label only the important, higher markers.
    if (loc.layer < map.getZoom() - 1) {
      labels.ensureLabel(m, map.getMapTypeId(), true);
    }

    m.setMap(map);
    m.addListener('click', function () {

      if (labels.hasLabel(m)) {
        self.emit('marker_activated', loc);
      } else {
        // First click shows the label
        labels.ensureLabel(m, map.getMapTypeId(), true);
      }

    });

    m.set('id', loc._id);
    _markers[loc._id] = m;

    return m;
  };

  var _removeMarker = function (m) {
    if (m) {
      // Remove from map
      m.setMap(null);

      // Remove click listener
      google.maps.event.clearInstanceListeners(m);

      delete _markers[m.get('id')];
    }
  };

  var _updateMarkers = function (locs) {
    // Add new markers and removes the excess.
    // To speed up things and avoid flicker,
    // only adds those markers on the screen that are not already there.

    var i, l, m, k;

    // For each location candidate
    for (i = 0; i < locs.length; i += 1) {
      l = locs[i];

      // If location already on the map
      if (_markers.hasOwnProperty(l._id)) {
        // Mark that it does not need to be removed.
        _markers[l._id].set('keep', true);
      } else {
        // otherwise, add it to the map.
        m = _addMarker(l);
        m.set('keep', true);
      }
    }

    // Remove markers that were not marked to be kept.
    // Also, reset keep for next update.
    for (i in _markers) {
      if (_markers.hasOwnProperty(i)) {
        m = _markers[i];
        k = m.get('keep');

        if (k) {
          // Reset for next update
          m.set('keep', false);
        } else {
          // Remove
          _removeMarker(m);
        }
      }
    }

  };

  var _loadMarkers = function () {
    var center = map.getCenter();
    var bounds = map.getBounds();
    var radius = Math.ceil(getBoundsDiagonal(bounds) / 2);
    var zoom = map.getZoom();

    markerStore.getWithin(center, radius, zoom, function (err, locs) {
      if (err) {
        return console.error(err);
      }  // else

      _updateMarkers(locs);
    });
  };

  // Bind

  // Listen for changes in locations so that the markers and labels
  // are up to date.
  markerStore.on('location_name_changed', function (ev) {
    // Parameters
    //   ev
    //     ev.data.newName
    var m, mloc;

    // No need to update if no such marker on the map.
    if (!_markers.hasOwnProperty(ev.locationId)) {
      return;
    }

    // Update name of markerLocation
    m = _markers[ev.locationId];
    mloc = m.get('location');
    mloc.name = ev.data.newName;

    // Refresh label. Force update even if label already visible
    labels.ensureLabel(m, map.getMapTypeId(), true);
  });

  markerStore.on('location_geom_changed', function (ev) {
    // Parameters
    //   ev
    //     ev.data.newGeom
    var m, g, mloc;

    // No need to update if no such marker on the map.
    if (!_markers.hasOwnProperty(ev.locationId)) {
      return;
    }

    // Update geom of markerLocation
    g = ev.data.newGeom;
    m = _markers[ev.locationId];
    mloc = m.get('location');
    mloc.geom = g;

    // Ensure coordinates are up to date
    m.setPosition({
      lat: g.coordinates[1],
      lng: g.coordinates[0],
    });
  });

  markerStore.on('location_created', function (ev) {
    var mloc = rawEventToMarkerLocation(ev);
    _addMarker(mloc);
  });

  markerStore.on('location_removed', function (ev) {
    if (_markers.hasOwnProperty(ev.locationId)) {
      var mToRemove = _markers[ev.locationId];
      _removeMarker(mToRemove);
    }
  });

  markerStore.on('location_tags_changed', function (ev) {
    // Update icon according to new tags.
    if (_markers.hasOwnProperty(ev.locationId)) {
      var m = _markers[ev.locationId];
      var mloc = m.get('location');
      // First update the location
      mloc.tags = ev.data.newTags;
      // Update icon according to the new tags
      m.setIcon(_chooseIcon(mloc));
    }
  });

  markerStore.on('location_entry_created', function (ev) {
    if (ev.data.isVisit && account.isMe(ev.user)) {
      // Add loc among visited locations if not visited before by this user.
      _visitedIds.add(ev.locationId);

      // Update marker icon to visited
      if (_markers.hasOwnProperty(ev.locationId)) {
        var m = _markers[ev.locationId];
        var mloc = m.get('location');
        m.setIcon(_chooseIcon(mloc));
      }
    }
  });

  markerStore.on('location_entry_changed', function (ev) {
    var m, mloc;

    // Change from non-visit to visit
    if (ev.data.newIsVisit && !ev.data.oldIsVisit && account.isMe(ev.user)) {
      // Add loc among visited locations if not visited before by this user.
      _visitedIds.add(ev.locationId);

      // Update marker icon to visited
      if (_markers.hasOwnProperty(ev.locationId)) {
        m = _markers[ev.locationId];
        mloc = m.get('location');
        m.setIcon(_chooseIcon(mloc));
      }
    } else {
      // From visit to non-visit
      if (ev.data.oldIsVisit && !ev.data.newIsVisit) {
        _visitedIds.remove(ev.locationId);

        // Update marker icon to unvisited
        if (_markers.hasOwnProperty(ev.locationId)) {
          m = _markers[ev.locationId];
          mloc = m.get('location');
          m.setIcon(_chooseIcon(mloc));
        }
      }
    }
  });

  // Listen map type change to invert label text colors.
  map.addListener('maptypeid_changed', function () {
    labels.updateMarkerLabels(_markers, map.getMapTypeId());
  });

  map.addListener('zoom_changed', function () {
    var z, k, m, loc;

    z = map.getZoom();

    // Listen zoom level change to only show labels of locations
    // with higher level than current zoom level.
    for (k in _markers) {
      if (_markers.hasOwnProperty(k)) {
        m = _markers[k];
        loc = m.get('location');
        if (loc.layer < z - 1) {
          // Ensure that label is visible.
          labels.ensureLabel(m, map.getMapTypeId());
        } else {
          labels.hideLabel(m);
        }
      }
    }

    // Listen zoom level change to update symbols of locations
    // with all children visible.
    for (k in _markers) {
      if (_markers.hasOwnProperty(k)) {
        m = _markers[k];
        loc = m.get('location');

        if (loc.childLayer === z || loc.childLayer - 1 === z) {
          // Ensure that the correct marker icon is used.
          // DEBUG console.log(loc.childLayer, z);
          m.setIcon(_chooseIcon(loc));
        }
      }
    }
  });

  // Track when map becomes usable.
  // See 'var _mapReady = false' above for details.
  google.maps.event.addListenerOnce(map, 'idle', function () {
    _mapReady = true;
  });

  // Public methods

  self.startLoading = function () {
    // Each idle, fetch a new set of locations.
    // The idle is emitted automatically after the initial map load.

    // Prevent duplicate listeners.
    if (_loaderListener !== null) {
      return;
    }

    // Each time map stops, fetch.
    _loaderListener = map.addListener('idle', _loadMarkers);
    // Load the first manually but only if the map is ready.
    // Map emits 'idle' when ready but user might not have been logged in yet.
    if (_mapReady) {
      _loadMarkers();
    }

    // Fetch the list of visited locations as soon as possible.
    account.getVisitedLocationIds(function (err, ids) {
      if (err) {
        console.error(err);
        return;
      }

      _visitedIds.set(ids);
    });
  };

  self.stopLoading = function () {
    google.maps.event.removeListener(_loaderListener);
    _loaderListener = null;
  };

  self.removeAll = function () {
    // Clear the map.
    for (var i in _markers) {
      if (_markers.hasOwnProperty(i)) {
        _removeMarker(_markers[i]);
      }
    }
  };

};
<commit_msg>Correct variable naming<commit_after>/* eslint-disable max-lines, max-statements, no-lonely-if */
/* global google */

// LocationMarkers is a marker manager.

var markerStore = tresdb.stores.markers;
var account = tresdb.stores.account;
var getBoundsDiagonal = require('./lib/getBoundsDiagonal');
var rawEventToMarkerLocation = require('./lib/rawEventToMarkerLocation');
var labels = require('./lib/labels');
var chooseIcon = require('./chooseIcon');
var VisitedManager = require('./VisitedManager');
var emitter = require('component-emitter');

module.exports = function (map) {

  // Init
  var self = this;
  emitter(self);

  // Location markers on the map. A mapping from id to google.maps.Marker
  var _markers = {};

  // Does the map load markers from the locations and display them on the map.
  // Will be set when client is ready to load markers (logged in)
  var _loaderListener = null;

  // True if map is ready for use. Will be switched to true at the first
  // 'idle' event. See addListenerOnce('idle', ...) below.
  // We need to track this for startLoadingMarkers: if map is ready
  // then load the markers immediately when called, otherwise wait for idle.
  var _mapReady = false;

  // Array of ids of locations that the user has visited.
  // Fetch them as soon as possible.
  var _visitedIds = new VisitedManager();

  // Private methods

  var _chooseIcon = function (mloc) {
    var zoomLevel = map.getZoom();
    return chooseIcon(mloc, zoomLevel, _visitedIds);
  };

  var _addMarker = function (mloc) {
    // Create marker and add it to the map.
    //
    // Parameters:
    //   mloc
    //     a MarkerLocation.
    // Return
    //   the created marker
    var lng, lat, m;

    lng = mloc.geom.coordinates[0];
    lat = mloc.geom.coordinates[1];

    m = new google.maps.Marker({
      position: new google.maps.LatLng(lat, lng),
      icon: _chooseIcon(mloc),
    });

    // Store the MarkerLocation for _id, name, and layer info.
    m.set('location', mloc);

    // Label only the important, higher markers.
    if (mloc.layer < map.getZoom() - 1) {
      labels.ensureLabel(m, map.getMapTypeId(), true);
    }

    m.setMap(map);
    m.addListener('click', function () {

      if (labels.hasLabel(m)) {
        self.emit('marker_activated', mloc);
      } else {
        // First click shows the label
        labels.ensureLabel(m, map.getMapTypeId(), true);
      }

    });

    m.set('id', mloc._id);
    _markers[mloc._id] = m;

    return m;
  };

  var _removeMarker = function (m) {
    if (m) {
      // Remove from map
      m.setMap(null);

      // Remove click listener
      google.maps.event.clearInstanceListeners(m);

      delete _markers[m.get('id')];
    }
  };

  var _updateMarkers = function (locs) {
    // Add new markers and removes the excess.
    // To speed up things and avoid flicker,
    // only adds those markers on the screen that are not already there.

    var i, l, m, k;

    // For each location candidate
    for (i = 0; i < locs.length; i += 1) {
      l = locs[i];

      // If location already on the map
      if (_markers.hasOwnProperty(l._id)) {
        // Mark that it does not need to be removed.
        _markers[l._id].set('keep', true);
      } else {
        // otherwise, add it to the map.
        m = _addMarker(l);
        m.set('keep', true);
      }
    }

    // Remove markers that were not marked to be kept.
    // Also, reset keep for next update.
    for (i in _markers) {
      if (_markers.hasOwnProperty(i)) {
        m = _markers[i];
        k = m.get('keep');

        if (k) {
          // Reset for next update
          m.set('keep', false);
        } else {
          // Remove
          _removeMarker(m);
        }
      }
    }

  };

  var _loadMarkers = function () {
    var center = map.getCenter();
    var bounds = map.getBounds();
    var radius = Math.ceil(getBoundsDiagonal(bounds) / 2);
    var zoom = map.getZoom();

    markerStore.getWithin(center, radius, zoom, function (err, locs) {
      if (err) {
        return console.error(err);
      }  // else

      _updateMarkers(locs);
    });
  };

  // Bind

  // Listen for changes in locations so that the markers and labels
  // are up to date.
  markerStore.on('location_name_changed', function (ev) {
    // Parameters
    //   ev
    //     ev.data.newName
    var m, mloc;

    // No need to update if no such marker on the map.
    if (!_markers.hasOwnProperty(ev.locationId)) {
      return;
    }

    // Update name of markerLocation
    m = _markers[ev.locationId];
    mloc = m.get('location');
    mloc.name = ev.data.newName;

    // Refresh label. Force update even if label already visible
    labels.ensureLabel(m, map.getMapTypeId(), true);
  });

  markerStore.on('location_geom_changed', function (ev) {
    // Parameters
    //   ev
    //     ev.data.newGeom
    var m, g, mloc;

    // No need to update if no such marker on the map.
    if (!_markers.hasOwnProperty(ev.locationId)) {
      return;
    }

    // Update geom of markerLocation
    g = ev.data.newGeom;
    m = _markers[ev.locationId];
    mloc = m.get('location');
    mloc.geom = g;

    // Ensure coordinates are up to date
    m.setPosition({
      lat: g.coordinates[1],
      lng: g.coordinates[0],
    });
  });

  markerStore.on('location_created', function (ev) {
    var mloc = rawEventToMarkerLocation(ev);
    _addMarker(mloc);
  });

  markerStore.on('location_removed', function (ev) {
    if (_markers.hasOwnProperty(ev.locationId)) {
      var mToRemove = _markers[ev.locationId];
      _removeMarker(mToRemove);
    }
  });

  markerStore.on('location_tags_changed', function (ev) {
    // Update icon according to new tags.
    if (_markers.hasOwnProperty(ev.locationId)) {
      var m = _markers[ev.locationId];
      var mloc = m.get('location');
      // First update the location
      mloc.tags = ev.data.newTags;
      // Update icon according to the new tags
      m.setIcon(_chooseIcon(mloc));
    }
  });

  markerStore.on('location_entry_created', function (ev) {
    if (ev.data.isVisit && account.isMe(ev.user)) {
      // Add loc among visited locations if not visited before by this user.
      _visitedIds.add(ev.locationId);

      // Update marker icon to visited
      if (_markers.hasOwnProperty(ev.locationId)) {
        var m = _markers[ev.locationId];
        var mloc = m.get('location');
        m.setIcon(_chooseIcon(mloc));
      }
    }
  });

  markerStore.on('location_entry_changed', function (ev) {
    var m, mloc;

    // Change from non-visit to visit
    if (ev.data.newIsVisit && !ev.data.oldIsVisit && account.isMe(ev.user)) {
      // Add loc among visited locations if not visited before by this user.
      _visitedIds.add(ev.locationId);

      // Update marker icon to visited
      if (_markers.hasOwnProperty(ev.locationId)) {
        m = _markers[ev.locationId];
        mloc = m.get('location');
        m.setIcon(_chooseIcon(mloc));
      }
    } else {
      // From visit to non-visit
      if (ev.data.oldIsVisit && !ev.data.newIsVisit) {
        _visitedIds.remove(ev.locationId);

        // Update marker icon to unvisited
        if (_markers.hasOwnProperty(ev.locationId)) {
          m = _markers[ev.locationId];
          mloc = m.get('location');
          m.setIcon(_chooseIcon(mloc));
        }
      }
    }
  });

  // Listen map type change to invert label text colors.
  map.addListener('maptypeid_changed', function () {
    labels.updateMarkerLabels(_markers, map.getMapTypeId());
  });

  map.addListener('zoom_changed', function () {
    var z, k, m, mloc;

    z = map.getZoom();

    // Listen zoom level change to only show labels of locations
    // with higher level than current zoom level.
    for (k in _markers) {
      if (_markers.hasOwnProperty(k)) {
        m = _markers[k];
        mloc = m.get('location');
        if (mloc.layer < z - 1) {
          // Ensure that label is visible.
          labels.ensureLabel(m, map.getMapTypeId());
        } else {
          labels.hideLabel(m);
        }
      }
    }

    // Listen zoom level change to update symbols of locations
    // when all their children become visible or hidden.
    for (k in _markers) {
      if (_markers.hasOwnProperty(k)) {
        m = _markers[k];
        mloc = m.get('location');

        // Ensure that the correct marker icon is used.
        // They are zoom-sensitive.
        m.setIcon(_chooseIcon(mloc));
      }
    }
  });

  // Track when map becomes usable.
  // See 'var _mapReady = false' above for details.
  google.maps.event.addListenerOnce(map, 'idle', function () {
    _mapReady = true;
  });

  // Public methods

  self.startLoading = function () {
    // Each idle, fetch a new set of locations.
    // The idle is emitted automatically after the initial map load.

    // Prevent duplicate listeners.
    if (_loaderListener !== null) {
      return;
    }

    // Each time map stops, fetch.
    _loaderListener = map.addListener('idle', _loadMarkers);
    // Load the first manually but only if the map is ready.
    // Map emits 'idle' when ready but user might not have been logged in yet.
    if (_mapReady) {
      _loadMarkers();
    }

    // Fetch the list of visited locations as soon as possible.
    account.getVisitedLocationIds(function (err, ids) {
      if (err) {
        console.error(err);
        return;
      }

      _visitedIds.set(ids);
    });
  };

  self.stopLoading = function () {
    google.maps.event.removeListener(_loaderListener);
    _loaderListener = null;
  };

  self.removeAll = function () {
    // Clear the map.
    for (var i in _markers) {
      if (_markers.hasOwnProperty(i)) {
        _removeMarker(_markers[i]);
      }
    }
  };

};
<|endoftext|>


<commit_before>/*
Wonder how this works?
Storage is the Prototype of both localStorage and sessionStorage.
Got it?
*/

(function() {
  'use strict';

  Storage.prototype.set = function(key, obj) {
    var t = typeof obj;
    if (t==='undefined' || obj===null ) this.removeItem(key);
    this.setItem(key, (t==='object')?JSON.stringify(obj):obj);
  };
  Storage.prototype.get = function(key) {
      var obj = this.getItem(key);
      try {
        var j = JSON.parse(obj);
        if (j && typeof j === "object") return j;
      } catch (e) { }
      return obj;
  };
  Storage.prototype.has = window.hasOwnProperty;
  Storage.prototype.remove = window.removeItem;

  Storage.prototype.keys = function(){
    return Object.keys(this.valueOf());
  };

})();
<commit_msg>Add localStorage.extend with recursive support (but no typechecking)<commit_after>/*
Wonder how this works?
Storage is the Prototype of both localStorage and sessionStorage.
Got it?
*/

(function() {
  'use strict';

  function extend(){
    for(var i=1; i<arguments.length; i++)
        for(var key in arguments[i])
            if(arguments[i].hasOwnProperty(key)) {
                if (typeof arguments[0][key] === 'object'
                    && typeof arguments[i][key] === 'object')
             				extend(arguments[0][key], arguments[i][key]);
                else
                   arguments[0][key] = arguments[i][key];
             }
    return arguments[0];
  }

  Storage.prototype.set = function(key, obj) {
    var t = typeof obj;
    if (t==='undefined' || obj===null ) this.removeItem(key);
    this.setItem(key, (t==='object')?JSON.stringify(obj):obj);
  };
  Storage.prototype.get = function(key) {
      var obj = this.getItem(key);
      try {
        var j = JSON.parse(obj);
        if (j && typeof j === "object") return j;
      } catch (e) { }
      return obj;
  };
  Storage.prototype.extend = function(key, obj_merge) {
    this.set(key,extend(this.get(key),obj_merge);
  };

  Storage.prototype.has = window.hasOwnProperty;
  Storage.prototype.remove = window.removeItem;

  Storage.prototype.keys = function(){
    return Object.keys(this.valueOf());
  };

})();
<|endoftext|>


<commit_before>enyo.kind({
	name: "Ares.ErrorPopup",
	kind: "onyx.Popup",
	modal: true,
	centered: true,
	floating: true,
	published: {
		errorMsg: "unknown error",
		detailsHtml: "",
		detailsText: ""
	},
	classes:"ares-classic-popup",
	components: [
	    {tag: "div", classes:"title", content: "Error"},
			{classes:"ares-error-popup", fit: true, components: [
				{name: "msg"},
				{classes:"ares-error-details", components:[
					{classes:"button", components:[
						{tag:"label", classes:"label", name: "detailsBtn", content: "Details", ontap: "toggleDetails", showing: false},
						{name:"detailsArrow", classes:"optionDownArrow", ontap: "toggleDetails", showing: false},
						{name: "detailsDrw", kind: "onyx.Drawer", open: false, showing:false, classes:"ares-error-drawer", components: [
							{name: "detailsText", kind: "onyx.TextArea", disabled: true, fit:true, classes:"ares-error-text"},
							{name: "detailsHtml", allowHtml: true, fit:true}
						]}
					]}
				]}
			]},
			{kind: "onyx.Toolbar", classes:"bottom-toolbar", components: [
				{name: "okButton", kind: "onyx.Button", content: "Close", ontap: "hideErrorPopup"}
			]}
	],
	create: function() {
		this.inherited(arguments);
	},
	errorMsgChanged: function (oldVal) {
		if (this.debug) this.log(oldVal, "->", this.errorMsg);
		this.$.msg.setContent(this.errorMsg);
	},
	detailsTextChanged: function() {
		this.updateDetailsDrw();
	},
	detailsHtmlChanged: function() {
		this.updateDetailsDrw();
	},
	updateDetailsDrw: function() {
		if (this.detailsText || this.detailsHtml) {
			this.$.detailsBtn.show();
			this.$.detailsArrow.show();
			this.$.detailsDrw.show();
			this.$.detailsDrw.setOpen(true);
			if (this.detailsHtml) {
				this.$.detailsHtml.setContent(this.detailsHtml);
				this.$.detailsHtml.show();
				this.$.detailsText.hide();
			} else {
				this.$.detailsText.setValue(this.detailsText);
				this.$.detailsText.show();
				this.$.detailsHtml.hide();
			}
		} else {
			this.$.detailsBtn.hide();
			this.$.detailsArrow.hide();
			this.$.detailsDrw.hide();
			this.$.detailsText.setValue("");
			this.$.detailsHtml.setContent("");
		}
	},
	toggleDetails: function() {
		this.$.detailsDrw.setOpen(!this.$.detailsDrw.open);
	},
	hideErrorPopup: function() {
		this.setErrorMsg();
		this.setDetailsText();
		this.setDetailsHtml();
		this.hide();
	},
	raise: function(evt) {
		var msg, err, text, html;
		if (typeof evt === 'object') {
			if (evt instanceof Error) {
				err = evt;
				msg = err.toString();
			} else {
				err = evt.err;
				msg = evt.msg || (err && err.toString());
			}
		}
		text = err && (err.text || err.stack);
		html = err && err.html;
		text = !html && text;
		this.setErrorMsg(msg);
		this.setDetailsHtml(html);
		this.setDetailsText(text);
		this.show();
	}
});
<commit_msg>ENYO-2605: Correct jshint errors in folder utilities.<commit_after>enyo.kind({
	name: "Ares.ErrorPopup",
	kind: "onyx.Popup",
	modal: true,
	centered: true,
	floating: true,
	published: {
		errorMsg: "unknown error",
		detailsHtml: "",
		detailsText: ""
	},
	classes:"ares-classic-popup",
	components: [
	    {tag: "div", classes:"title", content: "Error"},
			{classes:"ares-error-popup", fit: true, components: [
				{name: "msg"},
				{classes:"ares-error-details", components:[
					{classes:"button", components:[
						{tag:"label", classes:"label", name: "detailsBtn", content: "Details", ontap: "toggleDetails", showing: false},
						{name:"detailsArrow", classes:"optionDownArrow", ontap: "toggleDetails", showing: false},
						{name: "detailsDrw", kind: "onyx.Drawer", open: false, showing:false, classes:"ares-error-drawer", components: [
							{name: "detailsText", kind: "onyx.TextArea", disabled: true, fit:true, classes:"ares-error-text"},
							{name: "detailsHtml", allowHtml: true, fit:true}
						]}
					]}
				]}
			]},
			{kind: "onyx.Toolbar", classes:"bottom-toolbar", components: [
				{name: "okButton", kind: "onyx.Button", content: "Close", ontap: "hideErrorPopup"}
			]}
	],
	create: function() {
		this.inherited(arguments);
	},
	errorMsgChanged: function (oldVal) {
		if (this.debug) {
			this.log(oldVal, "->", this.errorMsg);
		}
		this.$.msg.setContent(this.errorMsg);
	},
	detailsTextChanged: function() {
		this.updateDetailsDrw();
	},
	detailsHtmlChanged: function() {
		this.updateDetailsDrw();
	},
	updateDetailsDrw: function() {
		if (this.detailsText || this.detailsHtml) {
			this.$.detailsBtn.show();
			this.$.detailsArrow.show();
			this.$.detailsDrw.show();
			this.$.detailsDrw.setOpen(true);
			if (this.detailsHtml) {
				this.$.detailsHtml.setContent(this.detailsHtml);
				this.$.detailsHtml.show();
				this.$.detailsText.hide();
			} else {
				this.$.detailsText.setValue(this.detailsText);
				this.$.detailsText.show();
				this.$.detailsHtml.hide();
			}
		} else {
			this.$.detailsBtn.hide();
			this.$.detailsArrow.hide();
			this.$.detailsDrw.hide();
			this.$.detailsText.setValue("");
			this.$.detailsHtml.setContent("");
		}
	},
	toggleDetails: function() {
		this.$.detailsDrw.setOpen(!this.$.detailsDrw.open);
	},
	hideErrorPopup: function() {
		this.setErrorMsg();
		this.setDetailsText();
		this.setDetailsHtml();
		this.hide();
	},
	raise: function(evt) {
		var msg, err, text, html;
		if (typeof evt === 'object') {
			if (evt instanceof Error) {
				err = evt;
				msg = err.toString();
			} else {
				err = evt.err;
				msg = evt.msg || (err && err.toString());
			}
		}
		text = err && (err.text || err.stack);
		html = err && err.html;
		text = !html && text;
		this.setErrorMsg(msg);
		this.setDetailsHtml(html);
		this.setDetailsText(text);
		this.show();
	}
});
<|endoftext|>


<commit_before>function DOMView(element, pixelsPerUnit) {

    element = element || document.body;
    var view = new Container(['render']);
    view.pixelsPerUnit = pixelsPerUnit || 1;

    console.log('DOMVIEW');
    console.log(view);
    view.abc = 1209;

    var dragData = {};
    var eventHandler = function(e) {
        var offset = $(element).offset();
        var mouseX = e.pageX - offset.left;
        var mouseY = e.pageY - offset.top;
        var x =  mouseX / view.pixelsPerUnit;
        var y = mouseY / view.pixelsPerUnit;
        var target = $(e.target).closest('.kang-visual');
        var model = target && target[0] && target[0].kngModel;
        var mouseData = {x:x, y:y, target:target, e:e, model:model};

        function click() {

            if (model) {
                kng.send({to:model.obj, name:'click'});
            }
            else {
                kng.send({to:'scene', name:'create', e:e, obj: {
                    name:'ball',
                    model: {
                            x:x, y:y
                        }
                    }
                });
            }
        }
        if (e.type=='click') click();

        if (e.type=='mousedown') {
            dragData = {start:mouseData};

        }

        if (e.type=='mousemove') {
            if (dragData.start && dragData.start.model) {
               var vx = (mouseData.x - dragData.start.x)/20;
               var vy = (mouseData.y - dragData.start.y)/20;

               dragData.start.model(1).vx = vx;
               dragData.start.model(1).vy = vy;
            }
        }

        if (e.type=='mouseleave') {

        }

        if (e.type=='mouseup' || e.type=='mouseleave') {
            if (dragData.start) {
                dragData.end = mouseData;
                var vx = (dragData.end.x - dragData.start.x)/20;
                var vy = (dragData.end.y - dragData.start.y)/20;

                if (!dragData.start.model) {
                        kng.send({to:'scene', name:'create', e:e, obj: [{
                            name:'ball',
                            model: {
                                    x:dragData.start.x, y:dragData.start.y, vx:vx, vy:vy
                                }
                            }  ]
                        });
                } else {
                   // model(1).vx *= 0.0;
                  //  model(1).vy *= 0.0;
                }
            }

            dragData.start = dragData.end = null;
        }

    };
    $(element).on('click mousedown mouseup mousemove mouseleave', eventHandler);

   var d = Math.random()*0.01;
    view.onRender = function(data) {
        var ppu = this.pixelsPerUnit;
        //this.pixelsPerUnit += d;
        if (this.pixelsPerUnit>3 || this.pixelsPerUnit<0.5) d*=-1;

        var style = data.style;
        var el = data.el;
        model = data.model();
        var oldModel = data.model(-1);
        var newModel = data.model(1);
        style.left = ~~(model.x * ppu) + 'px';
        style.top =  ~~(model.y * ppu + 10) + 'px';

        style.width = (model.w * ppu) + 'px'; //!!!doesn't should take w/h from images?
        style.height = (model.h * ppu) + 'px';

        if (model.dead) {
            style.backgroundColor = "rgba(" + _.random(50,100) +",0,0,0.7)";
            style.zIndex = '-10';
        }
        if(typeof model.opacity!='undefined' && model.opacity !== null)
            style.opacity = model.opacity;

        if((typeof model.text != 'undefined') &&  el.innerHTML != model.text
            /*&& oldModel.text != model.text*/) {
            el.innerHTML = model.text;
        }

        var vendorStylePrefix = 'Webkit';//!!TODO: do support for others browsers than webkit
        var transform = '';
        if (typeof model.rotation != 'undefined')
            transform += 'rotate(' + model.rotation  +  'deg)';
        style[vendorStylePrefix + 'Transform'] = transform;

        if(!model.collidable) {
            style.border = '2px solid gray';
        }

        if(model.fadeOut) {
            $(el).fadeOut(model.fadeOut);
            newModel.fadeOut = 0;
        }

        if(model.blink) {
            $(el).fadeOut(~~(model.blink/2)).fadeIn(~~(model.blink/2));
            newModel.blink = 0;
        }

        if(model.popup) {
            $(el).animate({opacity:1},model.popup[0]).fadeOut(model.popup[1]);
            newModel.opacity = null;
            newModel.popup = null;
        }

    }

    view.onAdd = function(data) {
        var el = data.el = document.createElement('div');
        data.style = el.style;

        var className = data.model.obj.className;
        el.className = className;

        data.style.backgroundColor = data.model().color;
        element.appendChild(el);
        el.kngModel = data.model;

        var img = data.model.obj.image;
        if (img)
            console.log('url("'+ img.src + '")');
        if (img)
            data.style.backgroundImage = 'url("'+ img.src + '")';

        if (data.model().shape == 'circle')
            data.style.borderRadius = '50%';
    };

    view.onRemove = function(data) {
        var el = data.el;
        if (!el.parentNode ){
            console.log(el, '!!! WHY SOMETIMES is parentNode NULL?');
        }
        el.parentNode && el.parentNode.removeChild(el);
    }

    view.cleanup = function() {
        view.each(function(data) {
            view.onRemove(data);
        });

        $(element).unbind('click mousedown mouseup mousemove mouseleave', eventHandler);
    }

    return view;
};

function DOMViewTest() {
    /*var view = new DOMView;
    alert(view.onRender);
    alert(view.render);      */
}

new DOMViewTest;
<commit_msg>creating object when clicking: removing extra code<commit_after>function DOMView(element, pixelsPerUnit) {

    element = element || document.body;
    var view = new Container(['render']);
    view.pixelsPerUnit = pixelsPerUnit || 1;

    console.log('DOMVIEW');
    console.log(view);
    view.abc = 1209;

    var dragData = {};
    var eventHandler = function(e) {
        var offset = $(element).offset();
        var mouseX = e.pageX - offset.left;
        var mouseY = e.pageY - offset.top;
        var x =  mouseX / view.pixelsPerUnit;
        var y = mouseY / view.pixelsPerUnit;
        var target = $(e.target).closest('.kang-visual');
        var model = target && target[0] && target[0].kngModel;
        var mouseData = {x:x, y:y, target:target, e:e, model:model};

        function click() {

            if (model) {
                kng.send({to:model.obj, name:'click'});
            }

        }
        if (e.type=='click') click();

        if (e.type=='mousedown') {
            dragData = {start:mouseData};

        }

        if (e.type=='mousemove') {
            if (dragData.start && dragData.start.model) {
               var vx = (mouseData.x - dragData.start.x)/20;
               var vy = (mouseData.y - dragData.start.y)/20;

               dragData.start.model(1).vx = vx;
               dragData.start.model(1).vy = vy;
            }
        }

        if (e.type=='mouseleave') {

        }

        if (e.type=='mouseup' || e.type=='mouseleave') {
            if (dragData.start) {
                dragData.end = mouseData;
                var vx = (dragData.end.x - dragData.start.x)/20;
                var vy = (dragData.end.y - dragData.start.y)/20;

                if (!dragData.start.model) {
                        kng.send({to:'scene', name:'create', e:e, obj: [{
                            name:'ball',
                            model: {
                                    x:dragData.start.x, y:dragData.start.y, vx:vx, vy:vy
                                }
                            }  ]
                        });
                } else {
                   // model(1).vx *= 0.0;
                  //  model(1).vy *= 0.0;
                }
            }

            dragData.start = dragData.end = null;
        }

    };
    $(element).on('click mousedown mouseup mousemove mouseleave', eventHandler);

   var d = Math.random()*0.01;
    view.onRender = function(data) {
        var ppu = this.pixelsPerUnit;
        //this.pixelsPerUnit += d;
        if (this.pixelsPerUnit>3 || this.pixelsPerUnit<0.5) d*=-1;

        var style = data.style;
        var el = data.el;
        model = data.model();
        var oldModel = data.model(-1);
        var newModel = data.model(1);
        style.left = ~~(model.x * ppu) + 'px';
        style.top =  ~~(model.y * ppu + 10) + 'px';

        style.width = (model.w * ppu) + 'px'; //!!!doesn't should take w/h from images?
        style.height = (model.h * ppu) + 'px';

        if (model.dead) {
            style.backgroundColor = "rgba(" + _.random(50,100) +",0,0,0.7)";
            style.zIndex = '-10';
        }
        if(typeof model.opacity!='undefined' && model.opacity !== null)
            style.opacity = model.opacity;

        if((typeof model.text != 'undefined') &&  el.innerHTML != model.text
            /*&& oldModel.text != model.text*/) {
            el.innerHTML = model.text;
        }

        var vendorStylePrefix = 'Webkit';//!!TODO: do support for others browsers than webkit
        var transform = '';
        if (typeof model.rotation != 'undefined')
            transform += 'rotate(' + model.rotation  +  'deg)';
        style[vendorStylePrefix + 'Transform'] = transform;

        if(!model.collidable) {
            style.border = '2px solid gray';
        }

        if(model.fadeOut) {
            $(el).fadeOut(model.fadeOut);
            newModel.fadeOut = 0;
        }

        if(model.blink) {
            $(el).fadeOut(~~(model.blink/2)).fadeIn(~~(model.blink/2));
            newModel.blink = 0;
        }

        if(model.popup) {
            $(el).animate({opacity:1},model.popup[0]).fadeOut(model.popup[1]);
            newModel.opacity = null;
            newModel.popup = null;
        }

    }

    view.onAdd = function(data) {
        var el = data.el = document.createElement('div');
        data.style = el.style;

        var className = data.model.obj.className;
        el.className = className;

        data.style.backgroundColor = data.model().color;
        element.appendChild(el);
        el.kngModel = data.model;

        var img = data.model.obj.image;
        if (img)
            console.log('url("'+ img.src + '")');
        if (img)
            data.style.backgroundImage = 'url("'+ img.src + '")';

        if (data.model().shape == 'circle')
            data.style.borderRadius = '50%';
    };

    view.onRemove = function(data) {
        var el = data.el;
        if (!el.parentNode ){
            console.log(el, '!!! WHY SOMETIMES is parentNode NULL?');
        }
        el.parentNode && el.parentNode.removeChild(el);
    }

    view.cleanup = function() {
        view.each(function(data) {
            view.onRemove(data);
        });

        $(element).unbind('click mousedown mouseup mousemove mouseleave', eventHandler);
    }

    return view;
};

function DOMViewTest() {
    /*var view = new DOMView;
    alert(view.onRender);
    alert(view.render);      */
}

new DOMViewTest;
<|endoftext|>


<commit_before><commit_msg>Made FSMComponent a real component<commit_after><|endoftext|>


<commit_before>import Abilities from 'parser/shared/modules/Abilities';
import EventsNormalizer from 'parser/core/EventsNormalizer';

const debug = false;

// TODO maybe update the Timeline tab to show these prepull casts?
/*
 * This normalizer attempts to track all potentially relevant casts that
 * happened before the pull. It then fabricates cast events in order to help
 * other modules (like cast efficiency) be more accurate.
 *
 * Additionally, because there is currently no ability info on whether or not a
 * spell is instant or has a cast time, we cannot (yet) accurately fabricate
 * the right type of event (begincast + cast vs only a cast).
 */
class PrePullCooldowns extends EventsNormalizer {

  /*
   * Abilities is an analyzer, which means that accessing it from a
   * normalizer like this one is not ideal design. For now, to avoid having to
   * rewrite Abilities (a core module heavily coupled with pretty much every
   * specialization), just be aware that using any Ability properties that
   * require event data (like .cooldown if it's a func, etc.) will *not*
   * produce intended results. In this normalizer I only access parts of the
   * spellbook with static values.
   */
  static dependencies = {
    abilities: Abilities,
  };

  normalize(events) {

    // region Build cooldown search arrays
    /*
     * Rather than constantly querying Abilities, here we simplify into two
     * new arrays that we can modify without messing with the spellbook
     */
    const buffSpells = [];
    const damageSpells = [];

    // TODO what filtering should we use to determine what spells we care about?
    for (const ability of this.abilities.activeAbilities) {
      if (ability.buffSpellId) {
        buffSpells.push({ castId: ability.spell.id, buffId: ability.buffSpellId });
      }
      if (ability.damageSpellIds) {
        damageSpells.push({ castId: ability.spell.id, damageIds: ability.damageSpellIds });
      }
    }
    // endregion

    const prepullCasts = [];
    let precastClassResources = null;

    const firstEventIndex = this.getFightStartIndex(events);
    const firstTimestamp = events[firstEventIndex].timestamp;
    const playerId = this.owner.playerId;

    for (let i = 0; i < events.length; i += 1) {
      const event = events[i];
      const targetId = event.targetID;
      const sourceId = event.sourceID;

      if (sourceId !== playerId) {
        continue;
      }

      if (event.type === 'applybuff') {
        // We rely on the buffapply normalizer to set the prepull property
        if (targetId !== playerId || !event.prepull) {
          continue;
        }

        for (let i = 0; i < buffSpells.length; i += 1) {
          if (buffSpells[i].buffId === event.ability.guid) {
            debug && console.debug(`Detected a precast buff cooldown: ${event.ability.name}`);
            prepullCasts.push(this.constructor._fabricateCastEvent(event, buffSpells[i].castId));
            buffSpells.splice(i, 1);
            break;
          }
        }
        continue;
      }

      if (event.type === 'cast') {

        /*
         * This will copy the first resource information to all precast events.
         * It's not pretty or 100% accurate, but it prevents errors on analyzers
         * that require resource information and usually wont be too far off.
         *
         * In the future, a better way to handle this would be to make sure any
         * analyzers that need the resource information check for .prepull, or
         * we gain access to spell data that lets us build the resource
         * information more accurately.
         */
        if (precastClassResources === null && event.classResources) {
          debug && console.debug("Setting prepull class resources to:", event.classResources);
          precastClassResources = event.classResources;
        }

        // If a cast is found for a damage spell, remove it from the search
        for (let i = 0; i < damageSpells.length; i += 1) {
          if (damageSpells[i].castId === event.ability.guid) {
            damageSpells.splice(i, 1);
            break;
          }
        }
        continue;
      }

      if (event.type === 'damage') {
        // If a damage event already has a cast event, it shouldn't be in the array
        for (let i = 0; i < damageSpells.length; i += 1) {
          if (damageSpells[i].damageIds.some(id => id === event.ability.guid)) {
            debug && console.debug(`Detected a precast damage cooldown: ${event.ability.name}`);
            prepullCasts.push(this.constructor._fabricateCastEvent(event, damageSpells[i].castId));
            damageSpells.splice(i, 1);
            break;
          }
        }
      }

    }

    /*
     * Potential issue: If players cast buffs long before the pull that have a
     * long duration, this normalizer doesn't know that and assumes they
     * cast it optimized right before the pull. This means that the next time
     * it gets cast we may get an error saying it was still on cooldown. To
     * resolve this, we could also listen for buffremove events and use that
     * to deduce the cast time. This would require ability info about durations.
     */
    // Working backwards, assume worst-case GCD timestamps
    let totalGCD = 0;
    for (let i = prepullCasts.length - 1; i >= 0; i -= 1) {
      const event = prepullCasts[i];
      const ability = this.abilities.getAbility(event.ability.guid);
      const gcd = (ability && ability.gcd && ability.gcd.base) || 1500;
      totalGCD += gcd;
      event.timestamp = firstTimestamp - totalGCD;
      event.classResources = precastClassResources;
    }
    events.unshift(...prepullCasts);
    return events;
  }

  static _fabricateCastEvent(event, abilityId = null) {
    const ability = {
      abilityIcon: event.ability.abilityIcon,
      guid: abilityId || event.ability.guid,
      name: event.ability.name,
      type: event.ability.type,
    };

    return {
      type: 'cast',
      ability: ability,
      sourceID: event.sourceID,
      sourceIsFriendly: event.sourceIsFriendly,
      targetID: event.targetID,
      targetIsFriendly: event.targetIsFriendly,

      // Custom properties:
      prepull: true,
      __fabricated: true,
    };
  }

}

export default PrePullCooldowns;
<commit_msg>support new buffSpellId allowing array<commit_after>import Abilities from 'parser/shared/modules/Abilities';
import EventsNormalizer from 'parser/core/EventsNormalizer';

const debug = false;

// TODO maybe update the Timeline tab to show these prepull casts?
/*
 * This normalizer attempts to track all potentially relevant casts that
 * happened before the pull. It then fabricates cast events in order to help
 * other modules (like cast efficiency) be more accurate.
 *
 * Additionally, because there is currently no ability info on whether or not a
 * spell is instant or has a cast time, we cannot (yet) accurately fabricate
 * the right type of event (begincast + cast vs only a cast).
 */
class PrePullCooldowns extends EventsNormalizer {

  /*
   * Abilities is an analyzer, which means that accessing it from a
   * normalizer like this one is not ideal design. For now, to avoid having to
   * rewrite Abilities (a core module heavily coupled with pretty much every
   * specialization), just be aware that using any Ability properties that
   * require event data (like .cooldown if it's a func, etc.) will *not*
   * produce intended results. In this normalizer I only access parts of the
   * spellbook with static values.
   */
  static dependencies = {
    abilities: Abilities,
  };

  normalize(events) {

    // region Build cooldown search arrays
    /*
     * Rather than constantly querying Abilities, here we simplify into two
     * new arrays that we can modify without messing with the spellbook
     */
    const buffSpells = [];
    const damageSpells = [];

    // TODO what filtering should we use to determine what spells we care about?
    for (const ability of this.abilities.activeAbilities) {
      if (ability.buffSpellId) {
        if(ability.buffSpellId instanceof Array) {
          ability.buffSpellId.forEach(buffId => {
            buffSpells.push({ castId: ability.spell.id, buffId: buffId });
          });
        } else {
          buffSpells.push({ castId: ability.spell.id, buffId: ability.buffSpellId });
        }
      }
      if (ability.damageSpellIds) {
        damageSpells.push({ castId: ability.spell.id, damageIds: ability.damageSpellIds });
      }
    }
    // endregion

    const prepullCasts = [];
    let precastClassResources = null;

    const firstEventIndex = this.getFightStartIndex(events);
    const firstTimestamp = events[firstEventIndex].timestamp;
    const playerId = this.owner.playerId;

    for (let i = 0; i < events.length; i += 1) {
      const event = events[i];
      const targetId = event.targetID;
      const sourceId = event.sourceID;

      if (sourceId !== playerId) {
        continue;
      }

      if (event.type === 'applybuff') {
        // We rely on the buffapply normalizer to set the prepull property
        if (targetId !== playerId || !event.prepull) {
          continue;
        }

        for (let i = 0; i < buffSpells.length; i += 1) {
          if (buffSpells[i].buffId === event.ability.guid) {
            debug && console.debug(`Detected a precast buff cooldown: ${event.ability.name}`);
            prepullCasts.push(this.constructor._fabricateCastEvent(event, buffSpells[i].castId));
            buffSpells.splice(i, 1);
            break;
          }
        }
        continue;
      }

      if (event.type === 'cast') {

        /*
         * This will copy the first resource information to all precast events.
         * It's not pretty or 100% accurate, but it prevents errors on analyzers
         * that require resource information and usually wont be too far off.
         *
         * In the future, a better way to handle this would be to make sure any
         * analyzers that need the resource information check for .prepull, or
         * we gain access to spell data that lets us build the resource
         * information more accurately.
         */
        if (precastClassResources === null && event.classResources) {
          debug && console.debug("Setting prepull class resources to:", event.classResources);
          precastClassResources = event.classResources;
        }

        // If a cast is found for a damage spell, remove it from the search
        for (let i = 0; i < damageSpells.length; i += 1) {
          if (damageSpells[i].castId === event.ability.guid) {
            damageSpells.splice(i, 1);
            break;
          }
        }
        continue;
      }

      if (event.type === 'damage') {
        // If a damage event already has a cast event, it shouldn't be in the array
        for (let i = 0; i < damageSpells.length; i += 1) {
          if (damageSpells[i].damageIds.some(id => id === event.ability.guid)) {
            debug && console.debug(`Detected a precast damage cooldown: ${event.ability.name}`);
            prepullCasts.push(this.constructor._fabricateCastEvent(event, damageSpells[i].castId));
            damageSpells.splice(i, 1);
            break;
          }
        }
      }

    }

    /*
     * Potential issue: If players cast buffs long before the pull that have a
     * long duration, this normalizer doesn't know that and assumes they
     * cast it optimized right before the pull. This means that the next time
     * it gets cast we may get an error saying it was still on cooldown. To
     * resolve this, we could also listen for buffremove events and use that
     * to deduce the cast time. This would require ability info about durations.
     */
    // Working backwards, assume worst-case GCD timestamps
    let totalGCD = 0;
    for (let i = prepullCasts.length - 1; i >= 0; i -= 1) {
      const event = prepullCasts[i];
      const ability = this.abilities.getAbility(event.ability.guid);
      const gcd = (ability && ability.gcd && ability.gcd.base) || 1500;
      totalGCD += gcd;
      event.timestamp = firstTimestamp - totalGCD;
      event.classResources = precastClassResources;
    }
    events.unshift(...prepullCasts);
    return events;
  }

  static _fabricateCastEvent(event, abilityId = null) {
    const ability = {
      abilityIcon: event.ability.abilityIcon,
      guid: abilityId || event.ability.guid,
      name: event.ability.name,
      type: event.ability.type,
    };

    return {
      type: 'cast',
      ability: ability,
      sourceID: event.sourceID,
      sourceIsFriendly: event.sourceIsFriendly,
      targetID: event.targetID,
      targetIsFriendly: event.targetIsFriendly,

      // Custom properties:
      prepull: true,
      __fabricated: true,
    };
  }

}

export default PrePullCooldowns;
<|endoftext|>


<commit_before>var async = require('async');
var request = require('request');
var cheerio = require('cheerio');

module.exports = function(self, options) {

  // This method fetches the specified URL, determines its best embedded
  // representation via oembetter, and on success invokes its callback with null
  // and an object containing the oembed API response from the service provider.
  //
  // If oembetter has no luck, open graph is used as a fallback.
  //
  // If options.alwaysIframe is true, the result is a simple
  // iframe of the URL. If options.iframeHeight is set, the iframe
  // has that height in pixels, otherwise it is left to CSS.
  //
  // Responses are automatically cached, by default for one hour. See the
  // cacheLifetime option to the module.

  self.query = function(req, url, options, mainCallback) {
    if (!mainCallback) {
      mainCallback = options;
      options = {};
    }
    if (!self.cache) {
      self.cache = self.apos.caches.get('oembed');
    }
    var response;
    // Tolerant URL handling
    url = self.apos.launder.url(url);
    if (!url) {
      return mainCallback("Video URL invalid");
    }
    var key = url + ':' + JSON.stringify(options);
    return async.series({
      checkCache: function(callback) {
        return self.cache.get(key, function(err, response) {
          if (err) {
            return callback(err);
          }
          if (response !== undefined) {
            return mainCallback(err, response);
          }
          return callback(null);
        });
      },
      fetch: function(callback) {
        if (options.alwaysIframe) {
          return self.iframe(req, url, options, function(err, _response) {
            if (err) {
              return callback(err);
            }
            response = _response;
            return callback(null);
          });
        }
        return self.oembetter.fetch(url, function(err, _response) {
          if (err) {
            // Try open graph as a fallback
            return self.openGraph(req, url, function(err, _response) {
              if (err) {
                return callback(err);
              }
              response = _response;
              return callback(null);
            });
          }
          response = _response;
          return callback(null);
        });
      },
      forceSsl: function(callback) {
        // Make non-secure URLs protocol relative and
        // let the browser upgrade them to https if needed
        function makeProtocolRelative(s) {
          s = s.replace(/^http\:\/\//, '//');
          return s.replace(/(["'])http\:\/\//g, '$1//');
        }
        if (response.thumbnail_url) {
          response.thumbnail_url = makeProtocolRelative(response.thumbnail_url);
        }
        if (response.html) {
          response.html = makeProtocolRelative(response.html);
        }
        return setImmediate(callback);
      },
      setCache: function(callback) {
        // cache oembed responses for one hour
        return self.cache.set(url, response, self.options.cacheLifetime, callback);
      }
    }, function(err) {
      if (err) {
        return mainCallback(err);
      }
      return mainCallback(err, response);
    });
  };

  // Given a URL, return a nice oembed response for it
  // based on its Open Graph tags, or the best we can
  // fake, based on the HTML markup of the page. Called
  // for you by `self.query` if `oembetter` is unsuccessful.

  self.openGraph = function(req, url, callback) {
    return request(url, function(err, response, body) {
      if (err) {
        return callback(err);
      }
      if (response.statusCode >= 400) {
        return callback(response.statusCode);
      }
      var $;
      try {
        $ = cheerio.load(body);
      } catch (e) {
        return callback('cheerio parsing error');
      }
      var title = $('meta[property="og:title"]').attr('content') ||
        $('title').text();
      if (!title) {
        // A common goof these days
        title = $('h1').text();
        if (!title) {
          // Oh c'mon
          title = url;
        }
      }
      var type = $('meta[property="og:type"]').attr('content') ||
        'website';

      var image = $('meta[property="og:image"]').attr('content');
      if (!image) {
        // Looks like cheerio doesn't do :first yet?
        var $img = $('img');
        if ($img.length) {
          image = $img.attr('src');
        }
      }
      if (image) {
        if (image.match(/^\w+:/)) {
          if (!image.match(/^https?:/)) {
            // No dangerous schemes
            image = undefined;
          }
        } else {
          // Relative URL
          image = require('url').resolve(url, image);
        }
      }
      if (!image) {
        image = undefined;
      }
      var description = $('meta[property="og:description"]').attr('content') || $('meta[name="description"]').attr('content');
      if (!description) {
        // Remove text that isn't text
        $('script').remove();
        $('styles').remove();
        description = $('body').text();
      }
      description = self.apos.utils.truncatePlaintext(description, 300);
      url = $('meta[property="og:url"]').attr('content') ||
        url;
      var markup = self.render(req, 'openGraphEmbed.html', { title: title, type: type, image: image, description: description, url: url });
      return callback(null, {
        thumbnail_url: image,
        title: title,
        type: 'rich',
        html: markup
      });
    });
  };

  // Given a URL, return an oembed response for it
  // which just iframes the URL given. Fetches the page
  // first to get the title property.
  //
  // If options.iframeHeight is set, use that # of
  // pixels, otherwise do not specify & let CSS do it.
  //
  // Called by `self.query` if the `alwaysIframe` option
  // is true.

  self.iframe = function(req, url, options, callback) {
    return request(url, function(err, response, body) {
      if (err) {
        return callback(err);
      }
      try {
        var $ = cheerio.load(body);
      } catch (e) {
        // don't let cheerio take us down if body is dodgy
      }
      var title = url;
      if ($) {
        title = $('meta[property="og:title"]').attr('content') ||
          $('title').text();
        if (!title) {
          // A common goof these days
          title = $('h1').text();
          if (!title) {
            // Oh c'mon
            title = url;
          }
        }
      }
      var style = '';
      var html = '';
      if (options.iframeHeight) {
        style = 'style="height:' + options.iframeHeight + 'px"';
      }
      html = html.replace('STYLE', style);
      return callback(null, {
        title: title,
        type: 'rich',
        html: html
      });
    });
  };

  // Returns browser-side javascript to load a given
  // cross-domain js file dynamically and then run
  // the javascript code in the `then` string argument.
  // `script` should be a URL pointing to the third-party
  // js file and may start with // to autoselect
  // http or https depending on how the page was loaded.
  //
  // You may supply an id attribute for the script tag.
  // Some services rely on these (infogr.am).
  //
  // You may also supply the ID of an element that the
  // script should be inserted immediately before. Some
  // services try to infer how they should behave from the
  // context the script tag is in (infogr.am).
  //
  // This code was inspired by the wufoo embed code and
  // is used to dynamically load wufoo and other services
  // that use js-based embed codes. See the oembetter
  // filter in `wufoo.js`.

  self.afterScriptLoads = function(script, beforeId, scriptId, then) {

    if (script.match(/^\/\//)) {
      script = "('https:' == d.location.protocol ? 'https:' : 'http:') + '" + script + "'";
    } else {
      script = "'" + script + "'";
    }
    if (scriptId) {
      scriptId = 's.id = "' + scriptId + '"; ';
    } else {
      scriptId = '';
    }
    var before;
    if (beforeId) {
      before = 'd.getElementById("' + beforeId + '")';
    } else {
      before = 'd.getElementsByTagName(t)[0]';
    }
    return '";
  };

};
<commit_msg>Documentation tweaks<commit_after>var async = require('async');
var request = require('request');
var cheerio = require('cheerio');

module.exports = function(self, options) {

  // This method fetches the specified URL, determines its best embedded
  // representation via `oembetter`, and on success invokes its callback with null
  // and an object containing the oembed API response from the service provider.
  //
  // If `oembetter` has no luck, open graph is used as a fallback.
  //
  // If `options.alwaysIframe` is true, the result is a simple
  // iframe of the URL. If `options.iframeHeight` is set, the iframe
  // has that height in pixels, otherwise it is left to CSS.
  //
  // The `options` object is passed on to `oembetter.fetch`.
  //
  // Responses are automatically cached, by default for one hour. See the
  // cacheLifetime option to the module.

  self.query = function(req, url, options, mainCallback) {
    if (!mainCallback) {
      mainCallback = options;
      options = {};
    }
    if (!self.cache) {
      self.cache = self.apos.caches.get('oembed');
    }
    var response;
    // Tolerant URL handling
    url = self.apos.launder.url(url);
    if (!url) {
      return mainCallback("Video URL invalid");
    }
    var key = url + ':' + JSON.stringify(options);
    return async.series({
      checkCache: function(callback) {
        return self.cache.get(key, function(err, response) {
          if (err) {
            return callback(err);
          }
          if (response !== undefined) {
            return mainCallback(err, response);
          }
          return callback(null);
        });
      },
      fetch: function(callback) {
        if (options.alwaysIframe) {
          return self.iframe(req, url, options, function(err, _response) {
            if (err) {
              return callback(err);
            }
            response = _response;
            return callback(null);
          });
        }
        return self.oembetter.fetch(url, function(err, _response) {
          if (err) {
            // Try open graph as a fallback
            return self.openGraph(req, url, function(err, _response) {
              if (err) {
                return callback(err);
              }
              response = _response;
              return callback(null);
            });
          }
          response = _response;
          return callback(null);
        });
      },
      forceSsl: function(callback) {
        // Make non-secure URLs protocol relative and
        // let the browser upgrade them to https if needed
        function makeProtocolRelative(s) {
          s = s.replace(/^http\:\/\//, '//');
          return s.replace(/(["'])http\:\/\//g, '$1//');
        }
        if (response.thumbnail_url) {
          response.thumbnail_url = makeProtocolRelative(response.thumbnail_url);
        }
        if (response.html) {
          response.html = makeProtocolRelative(response.html);
        }
        return setImmediate(callback);
      },
      setCache: function(callback) {
        // cache oembed responses for one hour
        return self.cache.set(url, response, self.options.cacheLifetime, callback);
      }
    }, function(err) {
      if (err) {
        return mainCallback(err);
      }
      return mainCallback(err, response);
    });
  };

  // Given a URL, return a nice oembed response for it
  // based on its Open Graph tags, or the best we can
  // fake, based on the HTML markup of the page. Called
  // for you by `self.query` if `oembetter` is unsuccessful.

  self.openGraph = function(req, url, callback) {
    return request(url, function(err, response, body) {
      if (err) {
        return callback(err);
      }
      if (response.statusCode >= 400) {
        return callback(response.statusCode);
      }
      var $;
      try {
        $ = cheerio.load(body);
      } catch (e) {
        return callback('cheerio parsing error');
      }
      var title = $('meta[property="og:title"]').attr('content') ||
        $('title').text();
      if (!title) {
        // A common goof these days
        title = $('h1').text();
        if (!title) {
          // Oh c'mon
          title = url;
        }
      }
      var type = $('meta[property="og:type"]').attr('content') ||
        'website';

      var image = $('meta[property="og:image"]').attr('content');
      if (!image) {
        // Looks like cheerio doesn't do :first yet?
        var $img = $('img');
        if ($img.length) {
          image = $img.attr('src');
        }
      }
      if (image) {
        if (image.match(/^\w+:/)) {
          if (!image.match(/^https?:/)) {
            // No dangerous schemes
            image = undefined;
          }
        } else {
          // Relative URL
          image = require('url').resolve(url, image);
        }
      }
      if (!image) {
        image = undefined;
      }
      var description = $('meta[property="og:description"]').attr('content') || $('meta[name="description"]').attr('content');
      if (!description) {
        // Remove text that isn't text
        $('script').remove();
        $('styles').remove();
        description = $('body').text();
      }
      description = self.apos.utils.truncatePlaintext(description, 300);
      url = $('meta[property="og:url"]').attr('content') ||
        url;
      var markup = self.render(req, 'openGraphEmbed.html', { title: title, type: type, image: image, description: description, url: url });
      return callback(null, {
        thumbnail_url: image,
        title: title,
        type: 'rich',
        html: markup
      });
    });
  };

  // Given a URL, return an oembed response for it
  // which just iframes the URL given. Fetches the page
  // first to get the title property.
  //
  // If options.iframeHeight is set, use that # of
  // pixels, otherwise do not specify & let CSS do it.
  //
  // Called by `self.query` if the `alwaysIframe` option
  // is true.

  self.iframe = function(req, url, options, callback) {
    return request(url, function(err, response, body) {
      if (err) {
        return callback(err);
      }
      try {
        var $ = cheerio.load(body);
      } catch (e) {
        // don't let cheerio take us down if body is dodgy
      }
      var title = url;
      if ($) {
        title = $('meta[property="og:title"]').attr('content') ||
          $('title').text();
        if (!title) {
          // A common goof these days
          title = $('h1').text();
          if (!title) {
            // Oh c'mon
            title = url;
          }
        }
      }
      var style = '';
      var html = '';
      if (options.iframeHeight) {
        style = 'style="height:' + options.iframeHeight + 'px"';
      }
      html = html.replace('STYLE', style);
      return callback(null, {
        title: title,
        type: 'rich',
        html: html
      });
    });
  };

  // Returns browser-side javascript to load a given
  // cross-domain js file dynamically and then run
  // the javascript code in the `then` string argument.
  // `script` should be a URL pointing to the third-party
  // js file and may start with // to autoselect
  // http or https depending on how the page was loaded.
  //
  // You may supply an id attribute for the script tag.
  // Some services rely on these (infogr.am).
  //
  // You may also supply the ID of an element that the
  // script should be inserted immediately before. Some
  // services try to infer how they should behave from the
  // context the script tag is in (infogr.am).
  //
  // This code was inspired by the wufoo embed code and
  // is used to dynamically load wufoo and other services
  // that use js-based embed codes. See the oembetter
  // filter in `wufoo.js`.

  self.afterScriptLoads = function(script, beforeId, scriptId, then) {

    if (script.match(/^\/\//)) {
      script = "('https:' == d.location.protocol ? 'https:' : 'http:') + '" + script + "'";
    } else {
      script = "'" + script + "'";
    }
    if (scriptId) {
      scriptId = 's.id = "' + scriptId + '"; ';
    } else {
      scriptId = '';
    }
    var before;
    if (beforeId) {
      before = 'd.getElementById("' + beforeId + '")';
    } else {
      before = 'd.getElementsByTagName(t)[0]';
    }
    return '";
  };

};
<|endoftext|>