<commit_before>(function (window, $, undefined) {
    'use strict';

    var Capestatus;

    Capestatus = function Capestatus(cockpit) {
        console.log("Loading Capestatus plugin in the browser.");

        // Instance variables
        this.cockpit = cockpit;
	this.lastPing = null;

        // Add required UI elements
        $("#footercontent").append(
	  '

Connection

 \

Temperature

 \

Depth:

 \

time

 \

 \

 \

 \
           ');

	$("#servoTilt").append(''); //attr style

	//$("#lights").append('

'); //attr class
    $('#navtoolbar').append('

');
        // Register the various event handlers
        this.listen();
	var capes = this;
        setInterval(function(){capes.updateConnectionStatus();}, 1000);

	setInterval(function () {
	    $("#localtime").text((new Date()).toLocaleTimeString(), 1000);
	});
    };

    //This pattern will hook events in the cockpit and pull them all back
    //so that the reference to this instance is available for further processing
    Capestatus.prototype.listen = function listen() {
        var capes = this;
	this.cockpit.socket.on('status', function(data) {
            capes.UpdateStatusIndicators(data);
        });
    };

    Capestatus.prototype.batteryLevel = function batteryLevel(voltage){
        if(voltage < 9)
            return "level1";
        if(voltage < 10)
            return "level2";
        if(voltage < 10.5)
            return "level3";
        if(voltage < 11.5)
            return "level4";
        return "level5";
    };

    Capestatus.prototype.UpdateStatusIndicators = function UpdateStatusIndicators(data){
	var self = this;
	if ('time' in data) {
	    $("#formattedRunTime").text(msToTime(data.time));
	};
	if ('vout' in data) {
	    $("#currentVoltage").text(data.vout.toFixed(1) + 'v');
	    $("#batteryIndicator").attr('class',self.batteryLevel(data.vout));
	};
	if ('iout' in data) $("#currentCurrent").text(data.iout.toFixed(3) + 'A');
	if ('servo' in data) {
	    var angle = (90/500)*data.servo+90//*-45;
	    $("#servoTiltImage").attr("style", "-webkit-transform: rotate("+angle+"deg); -moz-transform: rotate("+angle+"deg);transform: rotate("+angle+"deg)");
	};
	if ('cpuUsage' in data) $("#currentCpuUsage").text((data.cpuUsage*100).toFixed(0) + '%');
	if ('LIGP' in data) $("#brightnessIndicator").attr('class','level'+Math.ceil(data.LIGP*10));

	this.lastPing = new Date();
    };

    Capestatus.prototype.updateConnectionStatus = function(){
        var now = new Date();
        var delay = now - this.lastPing;
        if(delay>3000)
            $("#connectionHealth").attr("class","false");
        else
            $("#connectionHealth").attr("class","true");
    };

    window.Cockpit.plugins.push(Capestatus);

}(window, jQuery));
<commit_msg>Fixed bug that was introducing massive CPU usage<commit_after>(function (window, $, undefined) {
    'use strict';

    var Capestatus;

    Capestatus = function Capestatus(cockpit) {
        console.log("Loading Capestatus plugin in the browser.");

        // Instance variables
        this.cockpit = cockpit;
	this.lastPing = null;

        // Add required UI elements
        $("#footercontent").append(
	  '

Connection

 \

Temperature

 \

Depth:

 \

time

 \

 \

 \

 \
           ');

	$("#servoTilt").append(''); //attr style

	//$("#lights").append('

'); //attr class
    $('#navtoolbar').append('

');
        // Register the various event handlers
        this.listen();
	var capes = this;
        setInterval(function(){capes.updateConnectionStatus();}, 1000);

	setInterval(function () {
	    $("#localtime").text((new Date()).toLocaleTimeString());
	},1000);
    };

    //This pattern will hook events in the cockpit and pull them all back
    //so that the reference to this instance is available for further processing
    Capestatus.prototype.listen = function listen() {
        var capes = this;
	this.cockpit.socket.on('status', function(data) {
            capes.UpdateStatusIndicators(data);
        });
    };

    Capestatus.prototype.batteryLevel = function batteryLevel(voltage){
        if(voltage < 9)
            return "level1";
        if(voltage < 10)
            return "level2";
        if(voltage < 10.5)
            return "level3";
        if(voltage < 11.5)
            return "level4";
        return "level5";
    };

    Capestatus.prototype.UpdateStatusIndicators = function UpdateStatusIndicators(data){
	var self = this;
	if ('time' in data) {
	    $("#formattedRunTime").text(msToTime(data.time));
	};
	if ('vout' in data) {
	    $("#currentVoltage").text(data.vout.toFixed(1) + 'v');
	    $("#batteryIndicator").attr('class',self.batteryLevel(data.vout));
	};
	if ('iout' in data) $("#currentCurrent").text(data.iout.toFixed(3) + 'A');
	if ('servo' in data) {
	    var angle = (90/500)*data.servo+90//*-45;
	    $("#servoTiltImage").attr("style", "-webkit-transform: rotate("+angle+"deg); -moz-transform: rotate("+angle+"deg);transform: rotate("+angle+"deg)");
	};
	if ('cpuUsage' in data) $("#currentCpuUsage").text((data.cpuUsage*100).toFixed(0) + '%');
	if ('LIGP' in data) $("#brightnessIndicator").attr('class','level'+Math.ceil(data.LIGP*10));

	this.lastPing = new Date();
    };

    Capestatus.prototype.updateConnectionStatus = function(){
        var now = new Date();
        var delay = now - this.lastPing;
        if(delay>3000)
            $("#connectionHealth").attr("class","false");
        else
            $("#connectionHealth").attr("class","true");
    };

    window.Cockpit.plugins.push(Capestatus);

}(window, jQuery));
<|endoftext|>


<commit_before>'use strict';

var MapController = function($scope, $rootScope, $timeout, $compile, services, leafletData, leafletEvents) {
  $scope.drawnItems = new L.FeatureGroup();
  $scope.clusterHull = new L.FeatureGroup();

  $scope.geom = null;
  $scope.newGeom = null;

  $scope.mainGeoAggTotalCount = 0;

  var drawOptions = {
    draw: {
      draw: {
        polyline: false,
        polygon: false,
        circle: false, // Turns off this drawing tool
        marker: false,
        rectangle: {
          showArea: true
        }
      },
      edit: {
        featureGroup: $scope.drawnItems
      },
      trash: true
    }
  };

  $scope.updateDashboard = function(geoAggregation, polygon, latlng) {
    var pathname = window.location.pathname.trim();

    if (pathname.lastIndexOf('/') !== (pathname.length - 1)) {
      pathname = pathname + '/';
    }

    var path = '\'' + pathname + 'assets/javascript/views/bubble-chart.html\'';
    var content = '

';

    $scope.geoAggData = [];
    if (geoAggregation) {
      geoAggregation.buckets.forEach(
        function(bucket, index) {
          var item = {
            value: bucket.doc_count,
            name: bucket.key,
            group: 'Crime types'
          };

          $scope.geoAggData.push(item);
        }
      );
    }

    $scope.$apply(
      function() {
        leafletData.getMap().then(
          function(map) {
            if (!map.hasLayer($scope.clusterHull)) {
              map.addLayer($scope.clusterHull);
            }

            if ($scope.popup) {
              $scope.removeCurrentHull();
              map.closePopup($scope.popup);
            }

            if (polygon) {
              $scope.currentHull = polygon;
              $scope.clusterHull.addLayer(polygon);
            }

            $scope.popup = L.popup()
            .setLatLng(latlng)
            .setContent(content)
            .openOn(map);
          }
        );
      }
    );
  };

  $scope.clickClusterCallback = function(event, clusterData) {
    var polygon = clusterData.polygon;
    var latlng = L.latLng(clusterData.coords[0], clusterData.coords[1]);

    if (typeof polygon === 'array' || polygon.length == 0) {
      polygon = null;
    }

    if (clusterData.count > 3 && polygon) {
      $scope.geojson = polygon.toGeoJSON();

      services.clusterGeoAggregation($scope.geojson.geometry, $scope.query,
        function(result) {
          $scope.updateDashboard(result, polygon, latlng);
        }
      );
    } else {
      $scope.updateDashboard(null, polygon, latlng);
    }
  };

  $scope.$on('leafletDirectiveMap.popupopen',
    function(event, leafletEvent) {
      $scope.showCustomGeoAggregation = true;
      var newScope = $scope.$new();

      // Put the GeoAggregation data into newScope (ChartsController)
      newScope.geoAggData = $scope.geoAggData;

      $compile(leafletEvent.leafletEvent.popup._contentNode)(newScope);
    }
  );

  $scope.removeCurrentHull = function(event, leafletEvent) {
    if ($scope.clusterHull.hasLayer($scope.currentHull)) {
      $scope.clusterHull.removeLayer($scope.currentHull);
    }
  };

  $scope.$on('leafletDirectiveMap.popupclose', $scope.removeCurrentHull);

  $scope.closePopup = function() {
    if ($scope.popup) {
      leafletData.getMap().then(
        function(map) {
          map.closePopup($scope.popup);
        }
      );
    }
  };

  // Create a cluster layer
  $scope.createClusterLayer = function(geom, query, timeQuery) {
    var options = {
      subdomains: services.config().subdomains,
      useJsonP: false,
      clickCallback: $scope.clickClusterCallback,
      clusterTooltip: 'Click here to view a chart\nby type of crime in this area.',
      updateCountCallback: function(totalCount) {
        $rootScope.$emit('event:updateClusterCount', totalCount);
      },
      formatCount: function(count) {
        return $.number(count, 0, '.', '.');
      }
    };

    var clusterUrl = services.clusterUrl(geom, query);
    var cluster = L.tileCluster(clusterUrl, options);

    return  {
      name: 'goGeo Cluster Layer',
      type: 'custom',
      layer: cluster,
      visible: true
    }
  };

  $scope.gogeoLayers = {
    baselayers: {
      googleRoadmap: {
        name: 'Google Streets',
        layerType: 'ROADMAP',
        type: 'google'
      }
    },
    overlays: {
      cluster: $scope.createClusterLayer()
    }
  };

  L.drawLocal.draw.toolbar.buttons.rectangle = 'Draw an area.';
  L.drawLocal.edit.toolbar.buttons.edit = 'Edit area.'
  L.drawLocal.edit.toolbar.buttons.editDisabled = 'No area to edit.'
  L.drawLocal.edit.toolbar.buttons.remove = 'Delete area.'
  L.drawLocal.edit.toolbar.buttons.removeDisabled = 'No area to delete.'

  L.drawLocal.draw.handlers.rectangle.tooltip.start = 'Click and drag to draw an area';
  L.drawLocal.edit.handlers.edit.tooltip.text = 'Drag handles to edit area.';
  L.drawLocal.edit.handlers.remove.tooltip.text = 'Click on an area and then save to remove.';

  angular.extend($scope, {
    london: {
      lat: 51.505,
      lng: -0.12,
      zoom: 14
    },
    defaults: {
      maxZoom: 15
    },
    controls: drawOptions,
    layers: $scope.gogeoLayers
  });

  $scope.$watch('london.zoom',
    function(zoom) {
      $scope.handlerLayers(zoom);
      $scope.closePopup();

      $rootScope.$emit('event:updateZoom', zoom);
    }
  );

  $scope.getNeSwPoints = function(bounds) {
    var ne = [bounds._northEast.lng, bounds._northEast.lat];
    var sw = [bounds._southWest.lng, bounds._southWest.lat];

    return [ne, sw];
  };

  $scope.$on('leafletDirectiveMap.moveend',
    function() {
      var layersDrawn = $scope.drawnItems.getLayers();

      if (layersDrawn.length == 0) {
        leafletData.getMap().then(
          function(map) {
            $scope.points = $scope.getNeSwPoints(map.getBounds());
            $rootScope.$emit('event:updateGeoAggregation', $scope.geom, $scope.points, $scope.query)
          }
        );
      }
    }
  );

  $scope.handlerLayers = function(zoom) {
    if (zoom) {
      $scope.zoom = zoom;
    }
    var overlays = $scope.gogeoLayers.overlays;
    var toUpdate = false;

    if ($scope.geom !== $scope.newGeom) {
      $scope.geom = $scope.newGeom;
      toUpdate = true;
    }

    if (JSON.stringify($scope.query) !== JSON.stringify($scope.newQuery)) {
      $scope.query = $scope.newQuery;
      toUpdate = true;
    }

    if (JSON.stringify($scope.timeQuery) !== JSON.stringify($scope.newTimeQuery)) {
      $scope.timeQuery = $scope.newTimeQuery;
      toUpdate = true;
    }

    var boolQuery = null;

    if ($scope.query && $scope.timeQuery) {
      boolQuery = {
        query: {
          bool: {
            must: [ $scope.timeQuery.query, $scope.query.query ]
          }
        }
      };
    } else if ($scope.query) {
      boolQuery = $scope.query;
    } else if ($scope.timeQuery) {
      boolQuery = $scope.timeQuery;
    }

    if (toUpdate) {
      console.log('bool query', JSON.stringify(boolQuery, null, 2));
      $timeout(
        function() {
          delete overlays.cluster;
        },
        10
      );

      $timeout(
        function() {
          overlays.cluster = $scope.createClusterLayer($scope.geom, boolQuery);
          $rootScope.$emit('event:updateGeoAggregation', $scope.geom, $scope.points, boolQuery);
        },
        100
      );
    }
  };

  $scope.drawHandler = function(event, leafletEvent) {
    var layer = leafletEvent.leafletEvent.layer;

    if (layer) {
      $scope.drawnItems.clearLayers();
      $scope.drawnItems.addLayer(layer);
    } else {
      layer = $scope.drawnItems.getLayers()[0];
      $scope.closePopup();
      $scope.removeCurrentHull();
    }

    if (layer) {
      var geojson = layer.toGeoJSON();
      $scope.newGeom = JSON.stringify(geojson.geometry);

      if (window._gaq) {
        _gaq.push(['_trackEvent', 'police-uk', 'draw:created']);
      }
    } else {
      $scope.newGeom = null;
      $scope.closePopup();
      $scope.removeCurrentHull();
    }

    $scope.handlerLayers($scope.zoom);
  };

  $scope.$on('leafletDirectiveMap.draw:created', $scope.drawHandler);
  $scope.$on('leafletDirectiveMap.draw:deleted', $scope.drawHandler);
  $scope.$on('leafletDirectiveMap.draw:edited', $scope.drawHandler);

  $rootScope.$on('event:queryChanged',
    function(event, newQuery) {
      $scope.newQuery = newQuery;
      $scope.handlerLayers($scope.zoom);
      $scope.closePopup();
    }
  );

  $rootScope.$on('event:dateUpdated',
    function(event, minMonth, maxMonth) {
      $scope.newTimeQuery = {
        query: {
          range: {
            month: {
              gte: minMonth,
              lte: maxMonth
            }
          }
        }
      };

      $scope.handlerLayers($scope.zoom);
      $scope.closePopup();
    }
  );
};<commit_msg>Set initial zoom level<commit_after>'use strict';

var MapController = function($scope, $rootScope, $timeout, $compile, services, leafletData, leafletEvents) {
  $scope.drawnItems = new L.FeatureGroup();
  $scope.clusterHull = new L.FeatureGroup();

  $scope.geom = null;
  $scope.newGeom = null;

  $scope.mainGeoAggTotalCount = 0;

  var drawOptions = {
    draw: {
      draw: {
        polyline: false,
        polygon: false,
        circle: false, // Turns off this drawing tool
        marker: false,
        rectangle: {
          showArea: true
        }
      },
      edit: {
        featureGroup: $scope.drawnItems
      },
      trash: true
    }
  };

  $scope.updateDashboard = function(geoAggregation, polygon, latlng) {
    var pathname = window.location.pathname.trim();

    if (pathname.lastIndexOf('/') !== (pathname.length - 1)) {
      pathname = pathname + '/';
    }

    var path = '\'' + pathname + 'assets/javascript/views/bubble-chart.html\'';
    var content = '

';

    $scope.geoAggData = [];
    if (geoAggregation) {
      geoAggregation.buckets.forEach(
        function(bucket, index) {
          var item = {
            value: bucket.doc_count,
            name: bucket.key,
            group: 'Crime types'
          };

          $scope.geoAggData.push(item);
        }
      );
    }

    $scope.$apply(
      function() {
        leafletData.getMap().then(
          function(map) {
            if (!map.hasLayer($scope.clusterHull)) {
              map.addLayer($scope.clusterHull);
            }

            if ($scope.popup) {
              $scope.removeCurrentHull();
              map.closePopup($scope.popup);
            }

            if (polygon) {
              $scope.currentHull = polygon;
              $scope.clusterHull.addLayer(polygon);
            }

            $scope.popup = L.popup()
            .setLatLng(latlng)
            .setContent(content)
            .openOn(map);
          }
        );
      }
    );
  };

  $scope.clickClusterCallback = function(event, clusterData) {
    var polygon = clusterData.polygon;
    var latlng = L.latLng(clusterData.coords[0], clusterData.coords[1]);

    if (typeof polygon === 'array' || polygon.length == 0) {
      polygon = null;
    }

    if (clusterData.count > 3 && polygon) {
      $scope.geojson = polygon.toGeoJSON();

      services.clusterGeoAggregation($scope.geojson.geometry, $scope.query,
        function(result) {
          $scope.updateDashboard(result, polygon, latlng);
        }
      );
    } else {
      $scope.updateDashboard(null, polygon, latlng);
    }
  };

  $scope.$on('leafletDirectiveMap.popupopen',
    function(event, leafletEvent) {
      $scope.showCustomGeoAggregation = true;
      var newScope = $scope.$new();

      // Put the GeoAggregation data into newScope (ChartsController)
      newScope.geoAggData = $scope.geoAggData;

      $compile(leafletEvent.leafletEvent.popup._contentNode)(newScope);
    }
  );

  $scope.removeCurrentHull = function(event, leafletEvent) {
    if ($scope.clusterHull.hasLayer($scope.currentHull)) {
      $scope.clusterHull.removeLayer($scope.currentHull);
    }
  };

  $scope.$on('leafletDirectiveMap.popupclose', $scope.removeCurrentHull);

  $scope.closePopup = function() {
    if ($scope.popup) {
      leafletData.getMap().then(
        function(map) {
          map.closePopup($scope.popup);
        }
      );
    }
  };

  // Create a cluster layer
  $scope.createClusterLayer = function(geom, query, timeQuery) {
    var options = {
      subdomains: services.config().subdomains,
      useJsonP: false,
      clickCallback: $scope.clickClusterCallback,
      clusterTooltip: 'Click here to view a chart\nby type of crime in this area.',
      updateCountCallback: function(totalCount) {
        $rootScope.$emit('event:updateClusterCount', totalCount);
      },
      formatCount: function(count) {
        return $.number(count, 0, '.', '.');
      }
    };

    var clusterUrl = services.clusterUrl(geom, query);
    var cluster = L.tileCluster(clusterUrl, options);

    return  {
      name: 'goGeo Cluster Layer',
      type: 'custom',
      layer: cluster,
      visible: true
    }
  };

  $scope.gogeoLayers = {
    baselayers: {
      googleRoadmap: {
        name: 'Google Streets',
        layerType: 'ROADMAP',
        type: 'google'
      }
    },
    overlays: {
      cluster: $scope.createClusterLayer()
    }
  };

  L.drawLocal.draw.toolbar.buttons.rectangle = 'Draw an area.';
  L.drawLocal.edit.toolbar.buttons.edit = 'Edit area.'
  L.drawLocal.edit.toolbar.buttons.editDisabled = 'No area to edit.'
  L.drawLocal.edit.toolbar.buttons.remove = 'Delete area.'
  L.drawLocal.edit.toolbar.buttons.removeDisabled = 'No area to delete.'

  L.drawLocal.draw.handlers.rectangle.tooltip.start = 'Click and drag to draw an area';
  L.drawLocal.edit.handlers.edit.tooltip.text = 'Drag handles to edit area.';
  L.drawLocal.edit.handlers.remove.tooltip.text = 'Click on an area and then save to remove.';

  angular.extend($scope, {
    london: {
      lat: 51.505,
      lng: -0.12,
      zoom: 6
    },
    defaults: {
      maxZoom: 15
    },
    controls: drawOptions,
    layers: $scope.gogeoLayers
  });

  $scope.$watch('london.zoom',
    function(zoom) {
      $scope.handlerLayers(zoom);
      $scope.closePopup();

      $rootScope.$emit('event:updateZoom', zoom);
    }
  );

  $scope.getNeSwPoints = function(bounds) {
    var ne = [bounds._northEast.lng, bounds._northEast.lat];
    var sw = [bounds._southWest.lng, bounds._southWest.lat];

    return [ne, sw];
  };

  $scope.$on('leafletDirectiveMap.moveend',
    function() {
      var layersDrawn = $scope.drawnItems.getLayers();

      if (layersDrawn.length == 0) {
        leafletData.getMap().then(
          function(map) {
            $scope.points = $scope.getNeSwPoints(map.getBounds());
            $rootScope.$emit('event:updateGeoAggregation', $scope.geom, $scope.points, $scope.query)
          }
        );
      }
    }
  );

  $scope.handlerLayers = function(zoom) {
    if (zoom) {
      $scope.zoom = zoom;
    }
    var overlays = $scope.gogeoLayers.overlays;
    var toUpdate = false;

    if ($scope.geom !== $scope.newGeom) {
      $scope.geom = $scope.newGeom;
      toUpdate = true;
    }

    if (JSON.stringify($scope.query) !== JSON.stringify($scope.newQuery)) {
      $scope.query = $scope.newQuery;
      toUpdate = true;
    }

    if (JSON.stringify($scope.timeQuery) !== JSON.stringify($scope.newTimeQuery)) {
      $scope.timeQuery = $scope.newTimeQuery;
      toUpdate = true;
    }

    var boolQuery = null;

    if ($scope.query && $scope.timeQuery) {
      boolQuery = {
        query: {
          bool: {
            must: [ $scope.timeQuery.query, $scope.query.query ]
          }
        }
      };
    } else if ($scope.query) {
      boolQuery = $scope.query;
    } else if ($scope.timeQuery) {
      boolQuery = $scope.timeQuery;
    }

    if (toUpdate) {
      console.log('bool query', JSON.stringify(boolQuery, null, 2));
      $timeout(
        function() {
          delete overlays.cluster;
        },
        10
      );

      $timeout(
        function() {
          overlays.cluster = $scope.createClusterLayer($scope.geom, boolQuery);
          $rootScope.$emit('event:updateGeoAggregation', $scope.geom, $scope.points, boolQuery);
        },
        100
      );
    }
  };

  $scope.drawHandler = function(event, leafletEvent) {
    var layer = leafletEvent.leafletEvent.layer;

    if (layer) {
      $scope.drawnItems.clearLayers();
      $scope.drawnItems.addLayer(layer);
    } else {
      layer = $scope.drawnItems.getLayers()[0];
      $scope.closePopup();
      $scope.removeCurrentHull();
    }

    if (layer) {
      var geojson = layer.toGeoJSON();
      $scope.newGeom = JSON.stringify(geojson.geometry);

      if (window._gaq) {
        _gaq.push(['_trackEvent', 'police-uk', 'draw:created']);
      }
    } else {
      $scope.newGeom = null;
      $scope.closePopup();
      $scope.removeCurrentHull();
    }

    $scope.handlerLayers($scope.zoom);
  };

  $scope.$on('leafletDirectiveMap.draw:created', $scope.drawHandler);
  $scope.$on('leafletDirectiveMap.draw:deleted', $scope.drawHandler);
  $scope.$on('leafletDirectiveMap.draw:edited', $scope.drawHandler);

  $rootScope.$on('event:queryChanged',
    function(event, newQuery) {
      $scope.newQuery = newQuery;
      $scope.handlerLayers($scope.zoom);
      $scope.closePopup();
    }
  );

  $rootScope.$on('event:dateUpdated',
    function(event, minMonth, maxMonth) {
      $scope.newTimeQuery = {
        query: {
          range: {
            month: {
              gte: minMonth,
              lte: maxMonth
            }
          }
        }
      };

      $scope.handlerLayers($scope.zoom);
      $scope.closePopup();
    }
  );
};<|endoftext|>


<commit_before>const searchEntitySelector = '.js-search-entity'

const SearchAnalytics = {
  init() {
    this.pageNumber =
      new URLSearchParams(window.location.search).get('page') || 1
    const entities = document.querySelectorAll(searchEntitySelector)
    if (entities) {
      for (let i = 0; i < entities.length; i++) {
        entities[i].addEventListener('click', this.onClickHandler.bind(this))
      }
    }
  },

  pushAnalyticsEvent({
    searchResultRank,
    searchResultPageNumber,
    searchCategory,
  }) {
    window.dataLayer = window.dataLayer || []
    window.dataLayer.push({
      event: 'searchResultClick',
      searchResultPageNumber,
      searchResultRank,
      searchCategory,
    })
  },

  onClickHandler(e) {
    const target = e.target
    if (target) {
      this.pushAnalyticsEvent({
        searchResultRank: target.dataset.searchResultRank,
        searchResultPageNumber: this.pageNumber,
        searchCategory: target.dataset.searchCategory,
      })
    }
  },
}

module.exports = SearchAnalytics
<commit_msg>Correct search onclick event delegation by using currentTarget<commit_after>const searchEntitySelector = '.js-search-entity'

const SearchAnalytics = {
  init() {
    this.pageNumber =
      new URLSearchParams(window.location.search).get('page') || 1
    const entities = document.querySelectorAll(searchEntitySelector)
    if (entities) {
      for (let i = 0; i < entities.length; i++) {
        entities[i].addEventListener('click', this.onClickHandler.bind(this))
      }
    }
  },

  pushAnalyticsEvent({
    searchResultRank,
    searchResultPageNumber,
    searchCategory,
  }) {
    window.dataLayer = window.dataLayer || []
    window.dataLayer.push({
      event: 'searchResultClick',
      searchResultPageNumber,
      searchResultRank,
      searchCategory,
    })
  },

  onClickHandler(e) {
    const target = e.currentTarget
    if (target) {
      this.pushAnalyticsEvent({
        searchResultRank: target.dataset.searchResultRank,
        searchResultPageNumber: this.pageNumber,
        searchCategory: target.dataset.searchCategory,
      })
    }
  },
}

module.exports = SearchAnalytics
<|endoftext|>


<commit_before>(function() {
  'use strict';

  angular
    .module('mendel')
    .controller('MainController', MainController);

  /** @ngInject */
  function MainController($q, $scope, AuthService, Category, Context, hotkeys, Keyword, Review, Session, toastr) {
    var vm = this;

    vm.getNextContext = getNextContext;
    vm.getPrevContext = getPrevContext;
    vm.toggleSpecialCategory = toggleSpecialCategory;
    vm.editKeyword = editKeyword;
    vm.saveKeyword = saveKeyword;

    // Initialize
    init();

    function init () {

      // Wait for Session to be created
      var killSessionWatcher = $scope.$watch(AuthService.isAuthenticated, function () {

        if (Session.user) {

          // Get Categories
          getCategories()
          .then(function(categories) {
            // Put regular categories in view
            vm.categories = categories;
          });

          // Get the last Context the user reviewed
          getContext(Session.user.last_context_id);

          // Kill this watch when we have a Session
          killSessionWatcher();
        }
      });

      // Set up hotkeys
      hotkeys.bindTo($scope)
      .add({
        combo: [
          'command+left',
          'command+backspace',
          'ctrl+left',
          'ctrl+backspace',
          'shift+left',
          'shift+backspace',
        ],
        allowIn: ['INPUT'],
        description: 'Get the previous context',
        callback: getPrevContext
      })
      .add({
        combo: [
          'command+right',
          'command+enter',
          'ctrl+right',
          'ctrl+enter',
          'shift+right',
          'shift+enter',
        ],
        allowIn: ['INPUT'],
        description: 'Get the next context',
        callback: getNextContext
      })
      .add({
        combo: [
          'left',
        ],
        description: 'Get the previous context',
        callback: getPrevContext
      })
      .add({
        combo: [
          'right',
        ],
        description: 'Get the next context',
        callback: getNextContext
      })
      ;
    }

    // Controller Functions

    // Get Context
    function getContext (id) {

      // If no ID is passed in, create an "empty" context
      if (!id) {

        // Set previous context ID from last context
        var _prevContextId = angular.copy(vm.context.id);

        // Empty Context
        vm.context = {};
        vm.keyword = null;
        vm.context.prev_context_id = _prevContextId;

        return;
      }

      // Get context
      Context.get({id: id})
      .$promise
      .then(getContextSuccess)
      .catch(getContextError);

      function getContextSuccess (context) {

        vm.context = context;
        vm.keyword = angular.copy(context.keyword_given);
        vm.context.previouslySelectedCategories = preselect();

        function preselect () {
          var _prevCategories = [];

          // Load previously-selected categories
          angular.forEach(context.user_reviews, function(review){
            _prevCategories.push(review.category);
          });

          // Loop through categories, set "selected" if in prevously selected categories for context
          angular.forEach(vm.categories, function(category) {
            if (_prevCategories.indexOf(category.id) !== -1) {
              category.selected = true;
            }

            // Handle special categories
            if (_prevCategories.indexOf(vm.deleteCategory.id) !== -1) {
              vm.toggleSpecialCategory('delete');
            }
            else if (_prevCategories.indexOf(vm.idkCategory.id) !== -1) {
              vm.toggleSpecialCategory('idk');
            }
          });

          return _prevCategories;
        }

        return context;
      }

      function getContextError (error) {

        // Show Error Toast
        for (var i in error.data) {
          toastr.error(error.data[i], 'Error', {timeOut: 5000});
        }
      }
    }

    // Get Categories
    function getCategories () {

      // Set up deferred object to return
      var deferred = $q.defer();

      // Get categories
      Category.query()
      .$promise
      .then(getCategoriesSuccess)
      .catch(getCategoriesError);

      function getCategoriesSuccess (categories) {

        // Set up special categories
        vm.deleteCategory = null;
        vm.idkCategory = null;

        // Find special categories
        angular.forEach(categories, function (category) {

          /*
            TODO:
            Find another way to select the special categories.

            Currently, we're just searching for the categories
            with the names "Delete" and "I don't know"

            Additionally, it'd be nice if the special categories
            were protected from accidental (or intentional) deletion
            in the database/back end.
          */

          // Find the "Delete" category
          if (category.name === 'Delete') {
            vm.deleteCategory = category;
          }

          // Find the "I don't know" category
          if (category.name === 'I don\'t know') {
            vm.idkCategory = category;
          }

        });

        // Take special categories out of the mix of regular categories
        categories.splice(categories.indexOf(vm.deleteCategory), 1);
        categories.splice(categories.indexOf(vm.idkCategory), 1);

        // Resolve the promise with the categories
        return deferred.resolve(categories);
      }

      function getCategoriesError (error) {

        // Show Error Toast
        for (var i in error.data) {
          toastr.error(error.data[i], 'Error', {timeOut: 5000});
        }

        // Reject the promise
        return deferred.reject(error);
      }

      // Return the promise
      return deferred.promise;
    }

    // Edit Keyword
    function editKeyword () {

      vm.editingKeyword = true;

      vm.newKeyword = {
        name: vm.keyword.name
      };

    }

    // Save New Keyword
    function saveKeyword () {

      vm.editingKeyword = false;

      vm.keyword = vm.newKeyword;
    }

    // Get Next Context
    function getNextContext () {

      var _nextContextId = vm.context.next_context_id;

      // Check if the current context is not empty
      if (vm.context.id) {

        submitReviews()
        .then(function (success) {

          // Deselect all categories
          deselectAllCategories();

          // Reset special categories
          resetSpecialCategories();

          // Get next context
          getContext(_nextContextId);
        });
      }

      else {

        /*
          This case should never actually happen.
          The "Next" button is hidden when there is no "next context."
          But to be extra safe, we will show an error toast if this happens somehow.
        */

        // Show Error Toast
        toastr.error('Could not get the next context.', 'Error');
      }
    }

    // Get Previous Context
    function getPrevContext () {

      var _prevContextId = vm.context.prev_context_id;

      // Check if the current context is not empty
      if (vm.context.id) {

        submitReviews()
        .then(function (success) {

          // Deselect all categories
          deselectAllCategories();

          // Reset special categories
          resetSpecialCategories();

          // Get previous context
          getContext(_prevContextId);
        });
      }

      // Otherwise, just get the previous context
      else {

        // Get previous context
        getContext(_prevContextId);
      }
    }

    // Submit Reviews
    function submitReviews() {

      // Set up deferred object to return
      var deferred = $q.defer();

      var categories = [];

      // Check if a special category is selected
      if (vm.deleteCategory.selected) { categories.push(vm.deleteCategory.id); }
      if (vm.idkCategory.selected) { categories.push(vm.idkCategory.id); }

      // Build array of categories to submit
      angular.forEach(vm.categories, function (category) {
        if (category.selected) {
          categories.push(category.id);
        }
      });

      // Submit Reviews
      Context.submitReviews({
        id: vm.context.id,
        categories: categories,
        keyword_proposed: vm.keyword
      })
      .$promise
      .then(submitReviewsSuccess)
      .catch(submitReviewsError);

      function submitReviewsSuccess (data) {

        // Show Success Toast
        toastr.success('Saved categories for ' + data.keyword_proposed);

        // Resolve the promise
        return deferred.resolve(data);
      }

      function submitReviewsError (error) {

        toastr.error('Could not submit review. Check the browser console for more information.', 'Error');
        console.error(error);

        // Reject the promise
        return deferred.reject(error);
      }

      // Return the promise
      return deferred.promise;
    }

    // Deselect All Categories
    function deselectAllCategories () {

      angular.forEach(vm.categories, function (category) {
        category.selected = false;
      });
    }

    // Reset special categories ("Delete" / "I don't know")
    function resetSpecialCategories () {

      // Unselect special categories
      vm.deleteCategory.selected = false;
      vm.idkCategory.selected = false;

      // Unblur regular categories
      vm.specialCategorySelected = false;
    }

    // Select a special category ("Delete / "I don't know")
    function toggleSpecialCategory (modeClicked) {

      // Begin by resetting special category selections
      vm.deleteCategory.selected = false;
      vm.idkCategory.selected = false;

      // Next, check if we are unclicking the currently-selected special category
      if (vm.specialCategorySelected && vm.specialCategorySelected === modeClicked) {

        resetSpecialCategories();

        return;
      }

      // Otherwise, turn on special category mode
      else {

        // "Delete" Mode
        if (modeClicked === 'delete') {

          // Blur regular categories
          vm.specialCategorySelected = 'delete';
          vm.deleteCategory.selected = true;
        }

        // "I don't know" Mode
        if (modeClicked === 'idk') {

          // Blur regular categories
          vm.specialCategorySelected = 'idk';
          vm.idkCategory.selected = true;
        }

        // Unselect all regular categories when in special category mode
        angular.forEach(vm.categories, function(category) {
          if (category.selected) {
            category.selected = false;
          }
        });
      }
    }

  }
})();<commit_msg>Check if user edited keyword on previously-created reviews<commit_after>(function() {
  'use strict';

  angular
    .module('mendel')
    .controller('MainController', MainController);

  /** @ngInject */
  function MainController($q, $scope, AuthService, Category, Context, hotkeys, Keyword, Review, Session, toastr) {
    var vm = this;

    vm.getNextContext = getNextContext;
    vm.getPrevContext = getPrevContext;
    vm.toggleSpecialCategory = toggleSpecialCategory;
    vm.editKeyword = editKeyword;
    vm.saveKeyword = saveKeyword;

    // Initialize
    init();

    function init () {

      // Wait for Session to be created
      var killSessionWatcher = $scope.$watch(AuthService.isAuthenticated, function () {

        if (Session.user) {

          // Get Categories
          getCategories()
          .then(function(categories) {
            // Put regular categories in view
            vm.categories = categories;
          });

          // Get the last Context the user reviewed
          getContext(Session.user.last_context_id);

          // Kill this watch when we have a Session
          killSessionWatcher();
        }
      });

      // Set up hotkeys
      hotkeys.bindTo($scope)
      .add({
        combo: [
          'command+left',
          'command+backspace',
          'ctrl+left',
          'ctrl+backspace',
          'shift+left',
          'shift+backspace',
        ],
        allowIn: ['INPUT'],
        description: 'Get the previous context',
        callback: getPrevContext
      })
      .add({
        combo: [
          'command+right',
          'command+enter',
          'ctrl+right',
          'ctrl+enter',
          'shift+right',
          'shift+enter',
        ],
        allowIn: ['INPUT'],
        description: 'Get the next context',
        callback: getNextContext
      })
      .add({
        combo: [
          'left',
        ],
        description: 'Get the previous context',
        callback: getPrevContext
      })
      .add({
        combo: [
          'right',
        ],
        description: 'Get the next context',
        callback: getNextContext
      })
      ;
    }

    // Controller Functions

    // Get Context
    function getContext (id) {

      // If no ID is passed in, create an "empty" context
      if (!id) {

        // Set previous context ID from last context
        var _prevContextId = angular.copy(vm.context.id);

        // Empty Context
        vm.context = {};
        vm.keyword = null;
        vm.context.prev_context_id = _prevContextId;

        return;
      }

      // Get context
      Context.get({id: id})
      .$promise
      .then(getContextSuccess)
      .catch(getContextError);

      function getContextSuccess (context) {

        vm.context = context;
        vm.keyword = angular.copy(context.keyword_given);
        vm.context.previouslySelectedCategories = preselect();

        function preselect () {
          var _prevCategories = [];

          // Load previously-selected categories
          angular.forEach(context.user_reviews, function(review) {

            // Check if this context has reviews with a different keyword_proposed
            if (review.keyword_given !== review.keyword_proposed) {

              // Get the proposed keyword from API
              Keyword.get({id: review.keyword_proposed})
              .$promise
              .then(setUpdatedKeywordSuccess)
              .catch(setUpdatedKeywordError);
            }

            // Push this category to previously-selected categories
            _prevCategories.push(review.category);
          });

          // Callbacks for setting updated keyword from keyword_proposed
          function setUpdatedKeywordSuccess (keyword_proposed) {
            vm.keyword = keyword_proposed;
          }

          function setUpdatedKeywordError (error) {
            toastr.error('Could not get this context. Check the browser console for more information.', 'Error');
            console.error(error);
          }

          // Loop through categories, set "selected" if in prevously selected categories for context
          angular.forEach(vm.categories, function(category) {
            if (_prevCategories.indexOf(category.id) !== -1) {
              category.selected = true;
            }

            // Handle special categories
            if (_prevCategories.indexOf(vm.deleteCategory.id) !== -1) {
              vm.toggleSpecialCategory('delete');
            }
            else if (_prevCategories.indexOf(vm.idkCategory.id) !== -1) {
              vm.toggleSpecialCategory('idk');
            }
          });

          return _prevCategories;
        }

        return context;
      }

      function getContextError (error) {

        // Show Error Toast
        for (var i in error.data) {
          toastr.error(error.data[i], 'Error', {timeOut: 5000});
        }
      }
    }

    // Get Categories
    function getCategories () {

      // Set up deferred object to return
      var deferred = $q.defer();

      // Get categories
      Category.query()
      .$promise
      .then(getCategoriesSuccess)
      .catch(getCategoriesError);

      function getCategoriesSuccess (categories) {

        // Set up special categories
        vm.deleteCategory = null;
        vm.idkCategory = null;

        // Find special categories
        angular.forEach(categories, function (category) {

          /*
            TODO:
            Find another way to select the special categories.

            Currently, we're just searching for the categories
            with the names "Delete" and "I don't know"

            Additionally, it'd be nice if the special categories
            were protected from accidental (or intentional) deletion
            in the database/back end.
          */

          // Find the "Delete" category
          if (category.name === 'Delete') {
            vm.deleteCategory = category;
          }

          // Find the "I don't know" category
          if (category.name === 'I don\'t know') {
            vm.idkCategory = category;
          }

        });

        // Take special categories out of the mix of regular categories
        categories.splice(categories.indexOf(vm.deleteCategory), 1);
        categories.splice(categories.indexOf(vm.idkCategory), 1);

        // Resolve the promise with the categories
        return deferred.resolve(categories);
      }

      function getCategoriesError (error) {

        // Show Error Toast
        for (var i in error.data) {
          toastr.error(error.data[i], 'Error', {timeOut: 5000});
        }

        // Reject the promise
        return deferred.reject(error);
      }

      // Return the promise
      return deferred.promise;
    }

    // Edit Keyword
    function editKeyword () {

      vm.editingKeyword = true;

      vm.newKeyword = {
        name: vm.keyword.name
      };

    }

    // Save New Keyword
    function saveKeyword () {

      vm.editingKeyword = false;

      vm.keyword = vm.newKeyword;
    }

    // Get Next Context
    function getNextContext () {

      var _nextContextId = vm.context.next_context_id;

      // Check if the current context is not empty
      if (vm.context.id) {

        submitReviews()
        .then(function (success) {

          // Deselect all categories
          deselectAllCategories();

          // Reset special categories
          resetSpecialCategories();

          // Get next context
          getContext(_nextContextId);
        });
      }

      else {

        /*
          This case should never actually happen.
          The "Next" button is hidden when there is no "next context."
          But to be extra safe, we will show an error toast if this happens somehow.
        */

        // Show Error Toast
        toastr.error('Could not get the next context.', 'Error');
      }
    }

    // Get Previous Context
    function getPrevContext () {

      var _prevContextId = vm.context.prev_context_id;

      // Check if the current context is not empty
      if (vm.context.id) {

        submitReviews()
        .then(function (success) {

          // Deselect all categories
          deselectAllCategories();

          // Reset special categories
          resetSpecialCategories();

          // Get previous context
          getContext(_prevContextId);
        });
      }

      // Otherwise, just get the previous context
      else {

        // Get previous context
        getContext(_prevContextId);
      }
    }

    // Submit Reviews
    function submitReviews() {

      // Set up deferred object to return
      var deferred = $q.defer();

      var categories = [];

      // Check if a special category is selected
      if (vm.deleteCategory.selected) { categories.push(vm.deleteCategory.id); }
      if (vm.idkCategory.selected) { categories.push(vm.idkCategory.id); }

      // Build array of categories to submit
      angular.forEach(vm.categories, function (category) {
        if (category.selected) {
          categories.push(category.id);
        }
      });

      // Submit Reviews
      Context.submitReviews({
        id: vm.context.id,
        categories: categories,
        keyword_proposed: vm.keyword
      })
      .$promise
      .then(submitReviewsSuccess)
      .catch(submitReviewsError);

      function submitReviewsSuccess (data) {

        // Show Success Toast
        toastr.success('Saved categories for ' + data.keyword_proposed);

        // Resolve the promise
        return deferred.resolve(data);
      }

      function submitReviewsError (error) {

        toastr.error('Could not submit review. Check the browser console for more information.', 'Error');
        console.error(error);

        // Reject the promise
        return deferred.reject(error);
      }

      // Return the promise
      return deferred.promise;
    }

    // Deselect All Categories
    function deselectAllCategories () {

      angular.forEach(vm.categories, function (category) {
        category.selected = false;
      });
    }

    // Reset special categories ("Delete" / "I don't know")
    function resetSpecialCategories () {

      // Unselect special categories
      vm.deleteCategory.selected = false;
      vm.idkCategory.selected = false;

      // Unblur regular categories
      vm.specialCategorySelected = false;
    }

    // Select a special category ("Delete / "I don't know")
    function toggleSpecialCategory (modeClicked) {

      // Begin by resetting special category selections
      vm.deleteCategory.selected = false;
      vm.idkCategory.selected = false;

      // Next, check if we are unclicking the currently-selected special category
      if (vm.specialCategorySelected && vm.specialCategorySelected === modeClicked) {

        resetSpecialCategories();

        return;
      }

      // Otherwise, turn on special category mode
      else {

        // "Delete" Mode
        if (modeClicked === 'delete') {

          // Blur regular categories
          vm.specialCategorySelected = 'delete';
          vm.deleteCategory.selected = true;
        }

        // "I don't know" Mode
        if (modeClicked === 'idk') {

          // Blur regular categories
          vm.specialCategorySelected = 'idk';
          vm.idkCategory.selected = true;
        }

        // Unselect all regular categories when in special category mode
        angular.forEach(vm.categories, function(category) {
          if (category.selected) {
            category.selected = false;
          }
        });
      }
    }

  }
})();<|endoftext|>


<commit_before>var utils = require('../util/utils');
var redis = require('redis');

var DEFAULT_PREFIX = 'POMELO:STATUS';

var StatusManager = function(app, opts) {
  this.app = app;
  this.opts = opts || {};
  this.prefix = opts.prefix || DEFAULT_PREFIX;
  this.host = opts.host;
  this.port = opts.port;
  this.redis = null;
};

module.exports = StatusManager;

StatusManager.prototype.start = function(cb) {
	this.redis = redis.createClient(this.port, this.host, this.opts);
  var self = this;
  this.redis.once('ready', cb);
};

StatusManager.prototype.stop = function(cb) {
  if(this.redis) {
    this.redis.end();
    this.redis = null;
  }
  utils.invokeCallback(cb);
};

StatusManager.prototype.add = function(uid, sid ,cb) {
 	this.redis.sadd(genKey(this, uid), sid, function(err) {
    utils.invokeCallback(cb, err);
  });
};

StatusManager.prototype.leave = function(uid, sid, cb) {
	this.redis.srem(genKey(this, uid), sid, function(err) {
    utils.invokeCallback(cb, err);
  });
};

StatusManager.prototype.getSidsByUid = function(uid, cb) {
  this.redis.smembers(genKey(this, uid), function(err, list) {
    utils.invokeCallback(cb, err, list);
  });
};

var genKey = function(self, uid) {
  return self.prefix + ':' + uid;
};<commit_msg>Update statusManager.js<commit_after>var utils = require('../util/utils');
var redis = require('redis');

var DEFAULT_PREFIX = 'POMELO:STATUS';

var StatusManager = function(app, opts) {
  this.app = app;
  this.opts = opts || {};
  this.prefix = opts.prefix || DEFAULT_PREFIX;
  this.host = opts.host;
  this.port = opts.port;
  this.redis = null;
};

module.exports = StatusManager;

StatusManager.prototype.start = function(cb) {
	this.redis = redis.createClient(this.port, this.host, this.opts);
  var self = this;
  this.redis.once('ready', cb);
};

StatusManager.prototype.stop = function(force, cb) {
  if(this.redis) {
    this.redis.end();
    this.redis = null;
  }
  utils.invokeCallback(cb);
};

StatusManager.prototype.add = function(uid, sid ,cb) {
 	this.redis.sadd(genKey(this, uid), sid, function(err) {
    utils.invokeCallback(cb, err);
  });
};

StatusManager.prototype.leave = function(uid, sid, cb) {
	this.redis.srem(genKey(this, uid), sid, function(err) {
    utils.invokeCallback(cb, err);
  });
};

StatusManager.prototype.getSidsByUid = function(uid, cb) {
  this.redis.smembers(genKey(this, uid), function(err, list) {
    utils.invokeCallback(cb, err, list);
  });
};

var genKey = function(self, uid) {
  return self.prefix + ':' + uid;
};
<|endoftext|>


<commit_before>'use babel';

import Controller from './controller.js';

export default {
  config: {
    grammars: {
      type   : 'array',
      default: ['source.gfm'],
      items  : {
        type: 'string'
      }
    }
  },

  controler: null,

  activate() {
    this.controller = new Controller();
  },

  deactivate() {
    this.controller.destroy();
  },

  serialize() {
  }
};
<commit_msg>Add configs<commit_after>'use babel';

import Controller from './controller.js';

export default {
  config: {
    grammars: {
      type   : 'array',
      default: ['source.gfm'],
      items  : {
        type: 'string'
      }
    },
    minimumContentWidth: {
      type   : 'integer',
      default: 3,
      minimum: 3
    },
    useEastAsianWidth: {
      type   : 'boolean',
      default: false
    }
  },

  controler: null,

  activate() {
    this.controller = new Controller();
  },

  deactivate() {
    this.controller.destroy();
  },

  serialize() {
  }
};
<|endoftext|>


<commit_before>/*!
 * Module dependencies.
 */

var gaze = require('gaze'),
    path = require('path');

/**
 * AutoReload Middleware.
 *
 * Watches the file system for changes and notifies client.
 *
 * Options:
 *
 *   - `options` {Object}
 *     - `options.autoreload` {Boolean} to enable the middleware (default: true)
 */

module.exports = function(options) {
    var browserToggle = false,
        lastModified = Date.now(),
        watches = [ path.join(process.cwd(), 'www/**/*') ];

    // optional options parameter
    options = options || {};
    if (typeof options.autoreload !== 'boolean')
        options.autoreload = true;

    // enable AutoReload
    if (options.autoreload) {
        var watch = new gaze.Gaze(watches);

        // exposing watch to browser middleware
        options.watch = watch;

        watch.on('error', function(e) {
            if (options.emitter) {
                options.emitter.emit('error', e);
            }
        });

        // flag as outdated on all local file system changes
        watch.on('all', function(event, filepath) {
            lastModified = Date.now();

            options.filesToUpdate.push([Date.now(), filepath]);

            if (options.emitter) {
                options.emitter.emit('log', 'file changed', filepath);
            }
        });

        // stop watching when the server shutsdown
        options.emitter.on('close', function() {
            watch.close();
        });

        options.emitter.on('browserPrepare', function() {
            browserToggle = true;
        });
    }

    // the app constantly polls the server checking for the outdated state
    // if the app detects the outdated state to be true, it will force a reload on the page
    return function(req, res, next) {
        if (req.url.indexOf('/__api__/autoreload') === 0) {
            if (req.method === 'GET') {
                // by default, lastUpdated is undefined.
                // on the first non-autoreload request, it is timestamped.
                // when the first request is to autoreload, we timestamp
                // it to 0 because no content has ever been retrieved,
                // which means that the content on the device is out-of-date.
                if (!req.session.lastUpdated) {
                    req.session.lastUpdated = 0;
                }
            }
            else if (req.method === 'POST'){
                req.session.lastUpdated = Date.now();
            }

            if (browserToggle) {
                req.session.lastUpdated = 0;
                browserToggle = false;
            }

            res.writeHead(200, { 'Content-Type': 'text/json' });
            res.end(JSON.stringify({
                content: {
                    lastModified: lastModified,
                    lastUpdated: req.session.lastUpdated,
                    outdated: (req.session.lastUpdated < lastModified)
                }
            }));
        }
        else {
            // when lastUpdated is undefined, set it as up-to-date
            // since a legit resource request is going through
            if (!req.session.lastUpdated) {
                req.session.lastUpdated = Date.now();
            }
            next();
        }
    };
};
<commit_msg>[#144] - check for browser platform before manipulating request<commit_after>/*!
 * Module dependencies.
 */

var gaze = require('gaze'),
    path = require('path');

/**
 * AutoReload Middleware.
 *
 * Watches the file system for changes and notifies client.
 *
 * Options:
 *
 *   - `options` {Object}
 *     - `options.autoreload` {Boolean} to enable the middleware (default: true)
 */

module.exports = function(options) {
    var browserToggle = false,
        lastModified = Date.now(),
        watches = [ path.join(process.cwd(), 'www/**/*') ];

    // optional options parameter
    options = options || {};
    if (typeof options.autoreload !== 'boolean')
        options.autoreload = true;

    // enable AutoReload
    if (options.autoreload) {
        var watch = new gaze.Gaze(watches);

        // exposing watch to browser middleware
        options.watch = watch;

        watch.on('error', function(e) {
            if (options.emitter) {
                options.emitter.emit('error', e);
            }
        });

        // flag as outdated on all local file system changes
        watch.on('all', function(event, filepath) {
            lastModified = Date.now();

            options.filesToUpdate.push([Date.now(), filepath]);

            if (options.emitter) {
                options.emitter.emit('log', 'file changed', filepath);
            }
        });

        // stop watching when the server shutsdown
        options.emitter.on('close', function() {
            watch.close();
        });

        options.emitter.on('browserPrepare', function() {
            browserToggle = true;
        });
    }

    // the app constantly polls the server checking for the outdated state
    // if the app detects the outdated state to be true, it will force a reload on the page
    return function(req, res, next) {
        if (req.url.indexOf('/__api__/autoreload') === 0) {
            if (req.method === 'GET') {
                // by default, lastUpdated is undefined.
                // on the first non-autoreload request, it is timestamped.
                // when the first request is to autoreload, we timestamp
                // it to 0 because no content has ever been retrieved,
                // which means that the content on the device is out-of-date.
                if (!req.session.lastUpdated) {
                    req.session.lastUpdated = 0;
                }
            }
            else if (req.method === 'POST'){
                req.session.lastUpdated = Date.now();
            }

            if (browserToggle && isBrowser(req)) {
                req.session.lastUpdated = 0;
                browserToggle = false;
            }

            res.writeHead(200, { 'Content-Type': 'text/json' });
            res.end(JSON.stringify({
                content: {
                    lastModified: lastModified,
                    lastUpdated: req.session.lastUpdated,
                    outdated: (req.session.lastUpdated < lastModified)
                }
            }));
        }
        else {
            // when lastUpdated is undefined, set it as up-to-date
            // since a legit resource request is going through
            if (!req.session.lastUpdated) {
                req.session.lastUpdated = Date.now();
            }
            next();
        }
    };
};

// Helper function to detect whether the XHR is coming from the browser platform
function isBrowser(req) {
    return (/Macintosh|Windows NT/).test(req.headers['user-agent']);
}<|endoftext|>


<commit_before>var asimov = require('asimov');
var onHeaders = require('on-headers');

var server = asimov.config('server.instance');

function logRequestMiddleware (req, res, next) {

  var started = new Date();
  server.logger.low('static', 'Incoming ' + req.method.toUpperCase() + ' request @ ' + req.url);

  onHeaders(res, function () {
    server.logger.since('static', 'Responded to ' + req.method.toUpperCase() + ' request @ ' + req.url, started);
  });

  next();
}

module.exports = logRequestMiddleware;<commit_msg>add statuscode to response logger<commit_after>var asimov = require('asimov');
var onHeaders = require('on-headers');

var server = asimov.config('server.instance');

function logRequestMiddleware (req, res, next) {

  var started = new Date();
  server.logger.low('static', 'Incoming ' + req.method.toUpperCase() + ' request @ ' + req.url);

  onHeaders(res, function () {
    server.logger.since('static', 'Responded with ' + res.statusCode + ' to ' + req.method.toUpperCase() + ' request @ ' + req.url, started);
  });

  next();
}

module.exports = logRequestMiddleware;<|endoftext|>


<commit_before>'use babel';

export default {
  markerType: {
    type: 'string',
    default: 'highlight-over',
    enum: ['line', 'highlight-under', 'highlight-over', 'highlight-outline'],
    description: 'Marker type'
  }
};
<commit_msg>Default marker type change<commit_after>'use babel';

export default {
  markerType: {
    type: 'string',
    default: 'line',
    enum: ['line', 'highlight-under', 'highlight-over', 'highlight-outline'],
    description: 'Marker type'
  }
};
<|endoftext|>


<commit_before>import { Meteor } from 'meteor/meteor';
import { DDPServer } from 'meteor/ddp-server';
import { EJSON } from 'meteor/ejson';
import { Events, RedisPipe } from '../../constants';
import RedisSubscriptionManager from '../../redis/RedisSubscriptionManager';
import { getRedisPusher } from "../../redis/getRedisClient";
import getChannelName from '../../utils/getChannelName';
import Config from '../../config';

const getWriteFence = function (optimistic) {
    if (optimistic && DDPServer._CurrentWriteFence) {
        return DDPServer._CurrentWriteFence.get();
    }
    return null;
};

const dispatchEvents = function (fence, events, channels) {
    if (fence) {
        const write = fence.beginWrite();
        RedisSubscriptionManager.queue.queueTask(() => {
            events.forEach(({ event, dedicatedChannel }) => {
                channels.forEach(channelName => {
                    RedisSubscriptionManager.process(channelName, event);
                });
                RedisSubscriptionManager.process(dedicatedChannel, event);
            });
            write.committed();
        });
    }

    if (Config.externalRedisPublisher) {
        return;
    }

    Meteor.defer(() => {
        const client = getRedisPusher();
        events.forEach(({ event, dedicatedChannel }) => {
            const message = EJSON.stringify(event);
            channels.forEach(channelName => {
                client.publish(channelName, message);
            });
            client.publish(dedicatedChannel, message);
        });
    });
};

const dispatchUpdate = function (optimistic, collectionName, channels, docIds, fields) {
    const fence = getWriteFence(optimistic);
    const uid = fence ? RedisSubscriptionManager.uid : null;
    const events = docIds.map(docId => {
        const event = {
            [RedisPipe.EVENT]: Events.UPDATE,
            [RedisPipe.FIELDS]: fields,
            [RedisPipe.DOC]: { _id: docId },
            [RedisPipe.UID]: uid,
        };
        const dedicatedChannel = getChannelName(`${collectionName}::${docId}`);
        return { event, dedicatedChannel };
    });
    dispatchEvents(fence, events, channels);
};

const dispatchRemove = function (optimistic, collectionName, channels, docIds) {
    const fence = getWriteFence(optimistic);
    const uid = fence ? RedisSubscriptionManager.uid : null;
    const events = docIds.map(docId => {
        const event = {
            [RedisPipe.EVENT]: Events.REMOVE,
            [RedisPipe.DOC]: { _id: docId },
            [RedisPipe.UID]: uid,
        };
        const dedicatedChannel = getChannelName(`${collectionName}::${docId}`);
        return { event, dedicatedChannel };
    });
    dispatchEvents(fence, events, channels);
};

const dispatchInsert = function (optimistic, collectionName, channels, docId) {
    const fence = getWriteFence(optimistic);
    const uid = fence ? RedisSubscriptionManager.uid : null;
    const event = {
        [RedisPipe.EVENT]: Events.INSERT,
        [RedisPipe.DOC]: { _id: docId },
        [RedisPipe.UID]: uid,
    };
    const dedicatedChannel = getChannelName(`${collectionName}::${docId}`);
    const events = [{ event, dedicatedChannel }];
    dispatchEvents(fence, events, channels);
};

export { dispatchInsert, dispatchUpdate, dispatchRemove };
<commit_msg>redis-oplog: refactor dispatchers<commit_after>import { Meteor } from 'meteor/meteor';
import { DDPServer } from 'meteor/ddp-server';
import { EJSON } from 'meteor/ejson';
import { Events, RedisPipe } from '../../constants';
import RedisSubscriptionManager from '../../redis/RedisSubscriptionManager';
import { getRedisPusher } from "../../redis/getRedisClient";
import getChannelName from '../../utils/getChannelName';
import Config from '../../config';

const getWriteFence = function (optimistic) {
    if (optimistic && DDPServer._CurrentWriteFence) {
        return DDPServer._CurrentWriteFence.get();
    }
    return null;
};

const dispatchEvents = function (fence, collectionName, channels, events) {
    if (fence) {
        const write = fence.beginWrite();
        RedisSubscriptionManager.queue.queueTask(() => {
            try {
              events.forEach((event) => {
                channels.forEach(channelName => {
                  RedisSubscriptionManager.process(channelName, event);
                });
                const docId = event[RedisPipe.DOC]._id;
                const dedicatedChannel = getChannelName(`${collectionName}::${docId}`);
                RedisSubscriptionManager.process(dedicatedChannel, event);
              });
            } finally {
              write.committed();
            }
        });
    }

    if (Config.externalRedisPublisher) {
        return;
    }

    Meteor.defer(() => {
        const client = getRedisPusher();
        events.forEach((event) => {
            const message = EJSON.stringify(event);
            channels.forEach(channelName => {
                client.publish(channelName, message);
            });
            const docId = event[RedisPipe.DOC]._id;
            const dedicatedChannel = getChannelName(`${collectionName}::${docId}`);
            client.publish(dedicatedChannel, message);
        });
    });
};

const dispatchUpdate = function (optimistic, collectionName, channels, docIds, fields) {
    const fence = getWriteFence(optimistic);
    const uid = fence ? RedisSubscriptionManager.uid : null;
    const events = docIds.map(docId => ({
        [RedisPipe.EVENT]: Events.UPDATE,
        [RedisPipe.FIELDS]: fields,
        [RedisPipe.DOC]: { _id: docId },
        [RedisPipe.UID]: uid,
    }));
    dispatchEvents(fence, collectionName, channels, events);
};

const dispatchRemove = function (optimistic, collectionName, channels, docIds) {
    const fence = getWriteFence(optimistic);
    const uid = fence ? RedisSubscriptionManager.uid : null;
    const events = docIds.map(docId => ({
        [RedisPipe.EVENT]: Events.REMOVE,
        [RedisPipe.DOC]: { _id: docId },
        [RedisPipe.UID]: uid,
    }));
    dispatchEvents(fence, collectionName, channels, events);
};

const dispatchInsert = function (optimistic, collectionName, channels, docId) {
    const fence = getWriteFence(optimistic);
    const uid = fence ? RedisSubscriptionManager.uid : null;
    const event = {
        [RedisPipe.EVENT]: Events.INSERT,
        [RedisPipe.DOC]: { _id: docId },
        [RedisPipe.UID]: uid,
    };
    dispatchEvents(fence, collectionName, channels, [event]);
};

export { dispatchInsert, dispatchUpdate, dispatchRemove };
<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */

var BinaryParser = require('./binary_parser').BinaryParser,
  inspect = require('util').inspect,
  debug = require('util').debug;

/**
 * Machine id.
 *
 * Create a random 3-byte value (i.e. unique for this
 * process). Other drivers use a md5 of the machine id here, but
 * that would mean an asyc call to gethostname, so we don't bother.
 */

var MACHINE_ID = parseInt(Math.random() * 0xFFFFFF, 10);

/**
 * Constructor.
 *
 * @param {String} id (optional)
 */

function ObjectID (id) {

  // Throw an error if it's not a valid setup
  if(id != null && !id instanceof Date && (id.length != 12 && id.length != 24)) throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters in hex format");
  // Generate id based on the input
  if (null == id || id instanceof Date) {
    this.id = this.generate(id);
  } else if (/^[0-9a-fA-F]{24}$/.test(id)) {
    return ObjectID.createFromHexString(id);
  } else {
    this.id = id;
  }
};

/**
 * Updates the ObjectID index.
 *
 * @return {Number}
 */

ObjectID.prototype.get_inc = function() {
  return ObjectID.index = (ObjectID.index + 1) % 0xFFFFFF;
};

/**
 * Generates an ObjectId.
 *
 * @return {String}
 */

ObjectID.prototype.generate = function(time) {
  if (time instanceof Date) {
    var unixTime = parseInt(time.getTime()/1000, 10);
  } else {
  	var unixTime = parseInt(Date.now()/1000, 10);
  }

  var time4Bytes = BinaryParser.encodeInt(unixTime, 32, true, true);
  var machine3Bytes = BinaryParser.encodeInt(MACHINE_ID, 24, false);
  var pid2Bytes = BinaryParser.fromShort(process.pid);
  var index3Bytes = BinaryParser.encodeInt(this.get_inc(), 24, false, true);
  return time4Bytes + machine3Bytes + pid2Bytes + index3Bytes;
};

/**
 * Converts this ObjectId to a hex string.
 *
 * @return {String}
 */

ObjectID.prototype.toHexString = function() {
  var hexString = ''
    , number
    , value;

  for (var index = 0, len = this.id.length; index < len; index++) {
    value = BinaryParser.toByte(this.id.substr(index, 1));
    number = value <= 15
      ? '0' + value.toString(16)
      : value.toString(16);
    hexString = hexString + number;
  }

  return hexString;
};

/**
 * Converts this id to a string.
 *
 * @return {String}
 */

ObjectID.prototype.toString = function() {
  return this.toHexString();
};

/**
 * Converts to a string representation of this Id.
 */

ObjectID.prototype.inspect = ObjectID.prototype.toString;

/**
 * Converts to its JSON representation.
 */

ObjectID.prototype.toJSON = ObjectID.prototype.toString;

/**
 * Compares the equality of this ObjectID with `otherID`.
 *
 * @return {Bool}
 */

ObjectID.prototype.equals = function equals (otherID) {
  var id = (otherID instanceof ObjectID || otherID.toHexString)
    ? otherID.id
    : ObjectID.createFromHexString(otherID).id;

  return this.id === id;
}

/**
 * Returns the generation time in seconds that this
 * ID was generated.
 *
 * @return {Number}
 */

ObjectID.prototype.__defineGetter__("generationTime", function() {
  return BinaryParser.decodeInt(this.id.substring(0,4), 32, true, true) * 1000;
});

/**
 * Statics.
 */

ObjectID.index = 0;

ObjectID.createPk = function createPk () {
  return new ObjectID();
};

/**
 * Creates an ObjectID from a hex string representation
 * of an ObjectID.
 *
 * @param {String} hexString
 * @return {ObjectID}
 */

ObjectID.createFromHexString = function createFromHexString (hexString) {
  // Throw an error if it's not a valid setup
  if(hexString != null && hexString.length != 24) throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters in hex format");

  var len = hexString.length;

  if(len > 12*2) {
    throw new Error('Id cannot be longer than 12 bytes');
  }

  var result = ''
    , string
    , number;

  for (var index = 0; index < len; index += 2) {
    string = hexString.substr(index, 2);
    number = parseInt(string, 16);
    result += BinaryParser.fromByte(number);
  }

  return new ObjectID(result);
};

/**
 * Expose.
 */

exports.ObjectID = ObjectID;

<commit_msg>syntax cleanup<commit_after>/**
 * Module dependencies.
 */

var BinaryParser = require('./binary_parser').BinaryParser,
  inspect = require('util').inspect,
  debug = require('util').debug;

/**
 * Machine id.
 *
 * Create a random 3-byte value (i.e. unique for this
 * process). Other drivers use a md5 of the machine id here, but
 * that would mean an asyc call to gethostname, so we don't bother.
 */

var MACHINE_ID = parseInt(Math.random() * 0xFFFFFF, 10);

/**
 * Constructor.
 *
 * @param {String} id (optional)
 */

function ObjectID (id) {

  // Throw an error if it's not a valid setup
  if(id != null && !id instanceof Date && (id.length != 12 && id.length != 24)) throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters in hex format");
  // Generate id based on the input
  if (null == id || id instanceof Date) {
    this.id = this.generate(id);
  } else if (/^[0-9a-fA-F]{24}$/.test(id)) {
    return ObjectID.createFromHexString(id);
  } else {
    this.id = id;
  }
};

/**
 * Updates the ObjectID index.
 *
 * @return {Number}
 */

ObjectID.prototype.get_inc = function() {
  return ObjectID.index = (ObjectID.index + 1) % 0xFFFFFF;
};

/**
 * Generates an ObjectId.
 *
 * @return {String}
 */

ObjectID.prototype.generate = function(time) {

  if (time instanceof Date) {
    var unixTime = time.getTime();
  } else {
  	var unixTime = Date.now();
  }

  unixTime = parseInt(unixTime/1000,10);

  var time4Bytes = BinaryParser.encodeInt(unixTime, 32, true, true);
  var machine3Bytes = BinaryParser.encodeInt(MACHINE_ID, 24, false);
  var pid2Bytes = BinaryParser.fromShort(process.pid);
  var index3Bytes = BinaryParser.encodeInt(this.get_inc(), 24, false, true);
  return time4Bytes + machine3Bytes + pid2Bytes + index3Bytes;
};

/**
 * Converts this ObjectId to a hex string.
 *
 * @return {String}
 */

ObjectID.prototype.toHexString = function() {
  var hexString = ''
    , number
    , value;

  for (var index = 0, len = this.id.length; index < len; index++) {
    value = BinaryParser.toByte(this.id.substr(index, 1));
    number = value <= 15
      ? '0' + value.toString(16)
      : value.toString(16);
    hexString = hexString + number;
  }

  return hexString;
};

/**
 * Converts this id to a string.
 *
 * @return {String}
 */

ObjectID.prototype.toString = function() {
  return this.toHexString();
};

/**
 * Converts to a string representation of this Id.
 */

ObjectID.prototype.inspect = ObjectID.prototype.toString;

/**
 * Converts to its JSON representation.
 */

ObjectID.prototype.toJSON = ObjectID.prototype.toString;

/**
 * Compares the equality of this ObjectID with `otherID`.
 *
 * @return {Bool}
 */

ObjectID.prototype.equals = function equals (otherID) {
  var id = (otherID instanceof ObjectID || otherID.toHexString)
    ? otherID.id
    : ObjectID.createFromHexString(otherID).id;

  return this.id === id;
}

/**
 * Returns the generation time in seconds that this
 * ID was generated.
 *
 * @return {Number}
 */

ObjectID.prototype.__defineGetter__("generationTime", function() {
  return BinaryParser.decodeInt(this.id.substring(0,4), 32, true, true) * 1000;
});

/**
 * Statics.
 */

ObjectID.index = 0;

ObjectID.createPk = function createPk () {
  return new ObjectID();
};

/**
 * Creates an ObjectID from a hex string representation
 * of an ObjectID.
 *
 * @param {String} hexString
 * @return {ObjectID}
 */

ObjectID.createFromHexString = function createFromHexString (hexString) {
  // Throw an error if it's not a valid setup
  if(hexString != null && hexString.length != 24) throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters in hex format");

  var len = hexString.length;

  if(len > 12*2) {
    throw new Error('Id cannot be longer than 12 bytes');
  }

  var result = ''
    , string
    , number;

  for (var index = 0; index < len; index += 2) {
    string = hexString.substr(index, 2);
    number = parseInt(string, 16);
    result += BinaryParser.fromByte(number);
  }

  return new ObjectID(result);
};

/**
 * Expose.
 */

exports.ObjectID = ObjectID;

<|endoftext|>


<commit_before>/** @babel */

import MultiList from './multi-list'

export default class MultiListCollection {
  constructor (lists, didChangeSelection) {
    this.list = new MultiList(lists, (item, key) => {
      this.lastSelectedItem = item
      this.lastSelectedKey = key
      didChangeSelection && didChangeSelection(item, key)
    })
    const selectedKey = this.list.getSelectedListKey()
    const selectedItem = this.list.getSelectedItem()
    this.lastSelectedItem = selectedItem
    this.lastSelectedKey = selectedKey
    this.selectedKeys = new Set(selectedKey ? [selectedKey] : [])
    this.selectedItems = new Set(selectedItem ? [selectedItem] : [])
  }

  updateLists (lists, {suppressCallback} = {}) {
    this.list.updateLists(lists, {suppressCallback})
    this.updateSelections()
  }

  clearSelectedItems () {
    this.selectedItems = new Set()
  }

  clearSelectedKeys () {
    this.selectedKeys = new Set()
  }

  getSelectedItems () {
    return this.selectedItems
  }

  getSelectedKeys () {
    return this.selectedKeys
  }

  getItemsForKey (key) {
    return this.list.getItemsForKey(key)
  }

  getLastSelectedListKey () {
    return this.lastSelectedKey
  }

  getLastSelectedItem () {
    return this.lastSelectedItem
  }

  selectNextList ({wrap, addToExisting} = {}) {
    this.list.selectNextList({wrap})
    this.updateSelections({addToExisting})
  }

  selectPreviousList ({wrap, addToExisting} = {}) {
    this.list.selectPreviousList({wrap})
    this.updateSelections({addToExisting})
  }

  selectNextItem ({addToExisting, stopAtBounds} = {}) {
    this.list.selectNextItem({stopAtBounds})
    this.updateSelections({addToExisting})
  }

  selectPreviousItem ({addToExisting, stopAtBounds} = {}) {
    this.list.selectPreviousItem({stopAtBounds})
    this.updateSelections({addToExisting})
  }

  updateSelections ({addToExisting} = {}) {
    const selectedKey = this.list.getSelectedListKey()
    const selectedItem = this.list.getSelectedItem()
    this.selectItems(selectedItem ? [selectedItem] : [], {addToExisting, suppressCallback: true})
    this.selectKeys(selectedKey ? [selectedKey] : [], {addToExisting, suppressCallback: true})
  }

  selectItems (items, {addToExisting, suppressCallback} = {}) {
    if (!addToExisting) this.clearSelectedItems()
    items.forEach(item => this.selectedItems.add(item))
    const lastItem = items[items.length - 1]
    this.lastItem = lastItem
    this.list.selectItem(lastItem, {suppressCallback})
  }

  selectKeys (keys, {addToExisting, suppressCallback} = {}) {
    if (!addToExisting) this.clearSelectedKeys()
    keys.forEach(key => this.selectedKeys.add(key))
    const lastKey = keys[keys.length - 1]
    this.lastSelectedKey = lastKey
    this.list.selectListForKey(lastKey, {suppressCallback})
  }

  selectAllItemsForKey (key, addToExisting) {
    this.selectKeys([key], {addToExisting})
    this.selectItems(this.list.getItemsForKey(key), {addToExisting})
  }

  selectFirstItemForKey (key, {addToExisting} = {}) {
    this.selectKeys([key], {addToExisting})
    this.selectItems([this.list.getItemsForKey(key)[0]], {addToExisting})
  }

  selectItemsAndKeysInRange (endPoint1, endPoint2, addToExisting) {
    if (!addToExisting) {
      this.clearSelectedItems()
      this.clearSelectedKeys()
    }
    // TODO: optimize
    const listKeys = this.list.getListKeys()
    const index1 = listKeys.indexOf(endPoint1.key)
    const index2 = listKeys.indexOf(endPoint2.key)

    if (index1 < 0) throw new Error(`key "${endPoint1.key}" not found`)
    if (index2 < 0) throw new Error(`key "${endPoint2.key}" not found`)
    let startPoint, endPoint, startKeyIndex, endKeyIndex
    if (index1 < index2) {
      startPoint = endPoint1
      endPoint = endPoint2
      startKeyIndex = index1
      endKeyIndex = index2
    } else {
      startPoint = endPoint2
      endPoint = endPoint1
      startKeyIndex = index2
      endKeyIndex = index1
    }
    const startItemIndex = this.list.getItemIndexForKey(startPoint.key, startPoint.item)
    const endItemIndex = this.list.getItemIndexForKey(endPoint.key, endPoint.item)
    if (startItemIndex < 0) throw new Error(`item "${startPoint.item}" not found`)
    if (endItemIndex < 0) throw new Error(`item "${endPoint.item}" not found`)

    if (startKeyIndex === endKeyIndex) {
      const items = this.list.getItemsForKey(listKeys[startKeyIndex])
      const indexes = [startItemIndex, endItemIndex].sort()
      this.selectKeys([startPoint.key], {addToExisting: true, suppressCallback: true})
      this.selectItems(items.slice(indexes[0], indexes[1] + 1), {addToExisting: true})
      return
    }

    for (let i = startKeyIndex; i <= endKeyIndex; i++) {
      const key = listKeys[i]
      this.selectKeys([key], {addToExisting: true, suppressCallback: true})
      const items = this.list.getItemsForKey(key)
      if (i === startKeyIndex) {
        this.selectItems(items.slice(startItemIndex), {addToExisting: true})
      } else if (i === endKeyIndex) {
        this.selectItems(items.slice(0, endItemIndex + 1), {addToExisting: true})
      } else {
        this.selectItems(items, {addToExisting: true})
      }
    }
  }
}
<commit_msg>Fix typo<commit_after>/** @babel */

import MultiList from './multi-list'

export default class MultiListCollection {
  constructor (lists, didChangeSelection) {
    this.list = new MultiList(lists, (item, key) => {
      this.lastSelectedItem = item
      this.lastSelectedKey = key
      didChangeSelection && didChangeSelection(item, key)
    })
    const selectedKey = this.list.getSelectedListKey()
    const selectedItem = this.list.getSelectedItem()
    this.lastSelectedItem = selectedItem
    this.lastSelectedKey = selectedKey
    this.selectedKeys = new Set(selectedKey ? [selectedKey] : [])
    this.selectedItems = new Set(selectedItem ? [selectedItem] : [])
  }

  updateLists (lists, {suppressCallback} = {}) {
    this.list.updateLists(lists, {suppressCallback})
    this.updateSelections()
  }

  clearSelectedItems () {
    this.selectedItems = new Set()
  }

  clearSelectedKeys () {
    this.selectedKeys = new Set()
  }

  getSelectedItems () {
    return this.selectedItems
  }

  getSelectedKeys () {
    return this.selectedKeys
  }

  getItemsForKey (key) {
    return this.list.getItemsForKey(key)
  }

  getLastSelectedListKey () {
    return this.lastSelectedKey
  }

  getLastSelectedItem () {
    return this.lastSelectedItem
  }

  selectNextList ({wrap, addToExisting} = {}) {
    this.list.selectNextList({wrap})
    this.updateSelections({addToExisting})
  }

  selectPreviousList ({wrap, addToExisting} = {}) {
    this.list.selectPreviousList({wrap})
    this.updateSelections({addToExisting})
  }

  selectNextItem ({addToExisting, stopAtBounds} = {}) {
    this.list.selectNextItem({stopAtBounds})
    this.updateSelections({addToExisting})
  }

  selectPreviousItem ({addToExisting, stopAtBounds} = {}) {
    this.list.selectPreviousItem({stopAtBounds})
    this.updateSelections({addToExisting})
  }

  updateSelections ({addToExisting} = {}) {
    const selectedKey = this.list.getSelectedListKey()
    const selectedItem = this.list.getSelectedItem()
    this.selectItems(selectedItem ? [selectedItem] : [], {addToExisting, suppressCallback: true})
    this.selectKeys(selectedKey ? [selectedKey] : [], {addToExisting, suppressCallback: true})
  }

  selectItems (items, {addToExisting, suppressCallback} = {}) {
    if (!addToExisting) this.clearSelectedItems()
    items.forEach(item => this.selectedItems.add(item))
    const lastItem = items[items.length - 1]
    this.lastSelectedItem = lastItem
    this.list.selectItem(lastItem, {suppressCallback})
  }

  selectKeys (keys, {addToExisting, suppressCallback} = {}) {
    if (!addToExisting) this.clearSelectedKeys()
    keys.forEach(key => this.selectedKeys.add(key))
    const lastKey = keys[keys.length - 1]
    this.lastSelectedKey = lastKey
    this.list.selectListForKey(lastKey, {suppressCallback})
  }

  selectAllItemsForKey (key, addToExisting) {
    this.selectKeys([key], {addToExisting})
    this.selectItems(this.list.getItemsForKey(key), {addToExisting})
  }

  selectFirstItemForKey (key, {addToExisting} = {}) {
    this.selectKeys([key], {addToExisting})
    this.selectItems([this.list.getItemsForKey(key)[0]], {addToExisting})
  }

  selectItemsAndKeysInRange (endPoint1, endPoint2, addToExisting) {
    if (!addToExisting) {
      this.clearSelectedItems()
      this.clearSelectedKeys()
    }
    // TODO: optimize
    const listKeys = this.list.getListKeys()
    const index1 = listKeys.indexOf(endPoint1.key)
    const index2 = listKeys.indexOf(endPoint2.key)

    if (index1 < 0) throw new Error(`key "${endPoint1.key}" not found`)
    if (index2 < 0) throw new Error(`key "${endPoint2.key}" not found`)
    let startPoint, endPoint, startKeyIndex, endKeyIndex
    if (index1 < index2) {
      startPoint = endPoint1
      endPoint = endPoint2
      startKeyIndex = index1
      endKeyIndex = index2
    } else {
      startPoint = endPoint2
      endPoint = endPoint1
      startKeyIndex = index2
      endKeyIndex = index1
    }
    const startItemIndex = this.list.getItemIndexForKey(startPoint.key, startPoint.item)
    const endItemIndex = this.list.getItemIndexForKey(endPoint.key, endPoint.item)
    if (startItemIndex < 0) throw new Error(`item "${startPoint.item}" not found`)
    if (endItemIndex < 0) throw new Error(`item "${endPoint.item}" not found`)

    if (startKeyIndex === endKeyIndex) {
      const items = this.list.getItemsForKey(listKeys[startKeyIndex])
      const indexes = [startItemIndex, endItemIndex].sort()
      this.selectKeys([startPoint.key], {addToExisting: true, suppressCallback: true})
      this.selectItems(items.slice(indexes[0], indexes[1] + 1), {addToExisting: true})
      return
    }

    for (let i = startKeyIndex; i <= endKeyIndex; i++) {
      const key = listKeys[i]
      this.selectKeys([key], {addToExisting: true, suppressCallback: true})
      const items = this.list.getItemsForKey(key)
      if (i === startKeyIndex) {
        this.selectItems(items.slice(startItemIndex), {addToExisting: true})
      } else if (i === endKeyIndex) {
        this.selectItems(items.slice(0, endItemIndex + 1), {addToExisting: true})
      } else {
        this.selectItems(items, {addToExisting: true})
      }
    }
  }
}
<|endoftext|>


<commit_before>var RPC       = require('./rpc'),
    globals   = require('../../globals'),
    PeerArray = require('../../util/peerarray');

var FindValueRPC = module.exports = RPC.extend({

  initialize: function(queried_peer, target_id) {
    if (arguments.length === 0) {
      this.supr();
    } else {
      this.supr(queried_peer, 'FIND_VALUE', [target_id]);
    }
  },

  getTarget: function() {
    return this.getParams(0);
  },

  normalizeParams: function() {
    return {
      target : this.getTarget()
    };
  },

  handleNormalizedParams: function(params) {
    if (typeof params.target !== 'string' || !globals.REGEX_NODE_ID.test(params.target)) {
      this.reject(new Error('non valid findvalue query'));
    } else {
      this.params = [params.target];
    }
    return this;
  },

  normalizeResult: function() {
    var args   = this.getResult(),
        nodes  = args[0].getTripleArray(),
        result = args[1];
    if (result) {
      return {
        nodes : nodes,
        value : result.value,
        exp   : result.exp || -1
      };
    } else {
      return {
        nodes : nodes
      };
    }
  },

  handleNormalizedResult: function(result) {
    var nodes, value = null;
    try {
      if (result.nodes) {
        nodes = new PeerArray(result.nodes);
        if (result.value) {
          value = {
            value : result.value,
            exp   : result.exp
          };
        }
        this.resolve(nodes, value);
      } else {
        this.reject(new Error('non valid findvalue response'));
      }
    } catch(e) {
      return this.reject(new Error('non valid findvalue response'));
    }
  }

});<commit_msg>dont try/catch a resolve statement or never see what happened wrong<commit_after>var RPC       = require('./rpc'),
    globals   = require('../../globals'),
    PeerArray = require('../../util/peerarray');

var FindValueRPC = module.exports = RPC.extend({

  initialize: function(queried_peer, target_id) {
    if (arguments.length === 0) {
      this.supr();
    } else {
      this.supr(queried_peer, 'FIND_VALUE', [target_id]);
    }
  },

  getTarget: function() {
    return this.getParams(0);
  },

  normalizeParams: function() {
    return {
      target : this.getTarget()
    };
  },

  handleNormalizedParams: function(params) {
    if (typeof params.target !== 'string' || !globals.REGEX_NODE_ID.test(params.target)) {
      this.reject(new Error('non valid findvalue query'));
    } else {
      this.params = [params.target];
    }
    return this;
  },

  normalizeResult: function() {
    var args   = this.getResult(),
        nodes  = args[0].getTripleArray(),
        result = args[1];
    if (result) {
      return {
        nodes : nodes,
        value : result.value,
        exp   : result.exp || -1
      };
    } else {
      return {
        nodes : nodes
      };
    }
  },

  handleNormalizedResult: function(result) {
    var nodes, value = null;

    if (result.nodes) {
      try {
      nodes = new PeerArray(result.nodes);
      } catch(e) {
        return this.reject(new Error('non valid findvalue response'));
      }

      if (result.value) {
        value = {
          value : result.value,
          exp   : result.exp
        };
      }

      this.resolve(nodes, value);
    } else {
      this.reject(new Error('non valid findvalue response'));
    }
  }

});<|endoftext|>


<commit_before>define([
	'global/runloop',
	'config/types',
	'render/DomFragment/Attribute/helpers/determineNameAndNamespace',
	'render/DomFragment/Attribute/helpers/setStaticAttribute',
	'render/DomFragment/Attribute/helpers/determinePropertyName',
	'render/DomFragment/Attribute/helpers/getInterpolator',
	'render/DomFragment/Attribute/prototype/bind',
	'render/DomFragment/Attribute/prototype/update',
	'render/StringFragment/_StringFragment'
], function (
	runloop,
	types,
	determineNameAndNamespace,
	setStaticAttribute,
	determinePropertyName,
	getInterpolator,
	bind,
	update,
	StringFragment
) {

	'use strict';

	var DomAttribute, booleanAttributes;

	// via https://github.com/kangax/html-minifier/issues/63#issuecomment-37763316
	booleanAttributes = /allowFullscreen|async|autofocus|autoplay|checked|compact|controls|declare|default|defaultChecked|defaultMuted|defaultSelected|defer|disabled|draggable|enabled|formNoValidate|hidden|indeterminate|inert|isMap|itemScope|loop|multiple|muted|noHref|noResize|noShade|noValidate|noWrap|open|pauseOnExit|readOnly|required|reversed|scoped|seamless|selected|sortable|spellcheck|translate|trueSpeed|typeMustMatch|visible/;

	DomAttribute = function ( options ) {

		this.type = types.ATTRIBUTE;
		this.element = options.element;
		determineNameAndNamespace( this, options.name );

		// if it's an empty attribute, or just a straight key-value pair, with no
		// mustache shenanigans, set the attribute accordingly and go home
		if ( !options.value || typeof options.value === 'string' ) {
			setStaticAttribute( this, options );
			return;
		}

		// otherwise we need to do some work
		this.root = options.root;
		this.pNode = options.pNode;

		// share parentFragment with parent element
		this.parentFragment = this.element.parentFragment;

		this.fragment = new StringFragment({
			descriptor:   options.value,
			root:         this.root,
			owner:        this
		});

		// Store a reference to this attribute's interpolator, if its fragment
		// takes the form `{{foo}}`. This is necessary for two-way binding and
		// for correctly rendering HTML later
		this.interpolator = getInterpolator( this );

		// if we're not rendering (i.e. we're just stringifying), we can stop here
		if ( !this.pNode ) {
			return;
		}

		// special cases
		if ( this.name === 'value' ) {
			this.isValueAttribute = true;

			// TODO need to wait until afterwards to determine type, in case we
			// haven't initialised that attribute yet
			// <input type='file' value='{{value}}'>
			if ( this.pNode.tagName === 'INPUT' && this.pNode.type === 'file' ) {
				this.isFileInputValue = true;
			}
		}

		// can we establish this attribute's property name equivalent?
		determinePropertyName( this, options );

		// determine whether this attribute can be marked as self-updating
		this.selfUpdating = this.fragment.isSimple();

		// mark as ready
		this.ready = true;
	};

	DomAttribute.prototype = {
		bind: bind,
		update: update,

		updateBindings: function () {
			// if the fragment this attribute belongs to gets reassigned (as a result of
			// as section being updated via an array shift, unshift or splice), this
			// attribute needs to recognise that its keypath has changed
			this.keypath = this.interpolator.keypath || this.interpolator.ref;

			// if we encounter the special case described above, update the name attribute
			if ( this.propertyName === 'name' ) {
				// replace actual name attribute
				this.pNode.name = '{{' + this.keypath + '}}';
			}
		},

		reassign: function ( indexRef, newIndex, oldKeypath, newKeypath ) {
			if ( this.fragment ) {
				this.fragment.reassign( indexRef, newIndex, oldKeypath, newKeypath );

				if ( this.twoway ) {
					this.updateBindings();
				}
			}
		},

		teardown: function () {
			var i;

			if ( this.boundEvents ) {
				i = this.boundEvents.length;

				while ( i-- ) {
					this.pNode.removeEventListener( this.boundEvents[i], this.updateModel, false );
				}
			}

			// ignore non-dynamic attributes
			if ( this.fragment ) {
				this.fragment.teardown();
			}
		},

		bubble: function () {
			// If an attribute's text fragment contains a single item, we can
			// update the DOM immediately...
			if ( this.selfUpdating ) {
				this.update();
			}

			// otherwise we want to register it as a deferred attribute, to be
			// updated once all the information is in, to prevent unnecessary
			// DOM manipulation
			else if ( !this.deferred && this.ready ) {
				runloop.addAttribute( this );
				this.deferred = true;
			}
		},

		toString: function () {
			var escaped, interpolator;

			if ( this.value === null ) {
				return this.name;
			}

			// Special case - select values (should not be stringified)
			if ( this.name === 'value' && this.element.lcName === 'select' ) {
				return;
			}

			// Special case - radio names
			if ( this.name === 'name' && this.element.lcName === 'input' && ( interpolator = this.interpolator ) ) {
				return 'name={{' + ( interpolator.keypath || interpolator.ref ) + '}}';
			}

			// Special case - boolean attributes
			if ( this.fragment && booleanAttributes.test( this.name ) ) {
				return this.fragment.getValue() ? this.name : null;
			}

			if ( this.fragment ) {
				escaped = escape( this.fragment.toString() );
			} else {
				escaped = escape( this.value );
			}

			//TODO: What was Rich's itent in this line? Adding in quotes to make tests pass
			//return this.name + '=' + ( escaped.indexOf( ' ' ) !== -1 ? '"' + escaped + '"' : escaped );

			return this.name + '=' + '"' + escaped + '"';
		}
	};

	function escape ( string ) {
		return string
			.replace( /&/g, '&' )
			.replace( /"/g, '"' )
			.replace( /'/g, ''' );
	}

	return DomAttribute;

});
<commit_msg>stringify empty attributes correctly (#616)<commit_after>define([
	'global/runloop',
	'config/types',
	'render/DomFragment/Attribute/helpers/determineNameAndNamespace',
	'render/DomFragment/Attribute/helpers/setStaticAttribute',
	'render/DomFragment/Attribute/helpers/determinePropertyName',
	'render/DomFragment/Attribute/helpers/getInterpolator',
	'render/DomFragment/Attribute/prototype/bind',
	'render/DomFragment/Attribute/prototype/update',
	'render/StringFragment/_StringFragment'
], function (
	runloop,
	types,
	determineNameAndNamespace,
	setStaticAttribute,
	determinePropertyName,
	getInterpolator,
	bind,
	update,
	StringFragment
) {

	'use strict';

	var DomAttribute, booleanAttributes;

	// via https://github.com/kangax/html-minifier/issues/63#issuecomment-37763316
	booleanAttributes = /allowFullscreen|async|autofocus|autoplay|checked|compact|controls|declare|default|defaultChecked|defaultMuted|defaultSelected|defer|disabled|draggable|enabled|formNoValidate|hidden|indeterminate|inert|isMap|itemScope|loop|multiple|muted|noHref|noResize|noShade|noValidate|noWrap|open|pauseOnExit|readOnly|required|reversed|scoped|seamless|selected|sortable|spellcheck|translate|trueSpeed|typeMustMatch|visible/;

	DomAttribute = function ( options ) {

		this.type = types.ATTRIBUTE;
		this.element = options.element;
		determineNameAndNamespace( this, options.name );

		// if it's an empty attribute, or just a straight key-value pair, with no
		// mustache shenanigans, set the attribute accordingly and go home
		if ( !options.value || typeof options.value === 'string' ) {
			setStaticAttribute( this, options );
			return;
		}

		// otherwise we need to do some work
		this.root = options.root;
		this.pNode = options.pNode;

		// share parentFragment with parent element
		this.parentFragment = this.element.parentFragment;

		this.fragment = new StringFragment({
			descriptor:   options.value,
			root:         this.root,
			owner:        this
		});

		// Store a reference to this attribute's interpolator, if its fragment
		// takes the form `{{foo}}`. This is necessary for two-way binding and
		// for correctly rendering HTML later
		this.interpolator = getInterpolator( this );

		// if we're not rendering (i.e. we're just stringifying), we can stop here
		if ( !this.pNode ) {
			return;
		}

		// special cases
		if ( this.name === 'value' ) {
			this.isValueAttribute = true;

			// TODO need to wait until afterwards to determine type, in case we
			// haven't initialised that attribute yet
			// <input type='file' value='{{value}}'>
			if ( this.pNode.tagName === 'INPUT' && this.pNode.type === 'file' ) {
				this.isFileInputValue = true;
			}
		}

		// can we establish this attribute's property name equivalent?
		determinePropertyName( this, options );

		// determine whether this attribute can be marked as self-updating
		this.selfUpdating = this.fragment.isSimple();

		// mark as ready
		this.ready = true;
	};

	DomAttribute.prototype = {
		bind: bind,
		update: update,

		updateBindings: function () {
			// if the fragment this attribute belongs to gets reassigned (as a result of
			// as section being updated via an array shift, unshift or splice), this
			// attribute needs to recognise that its keypath has changed
			this.keypath = this.interpolator.keypath || this.interpolator.ref;

			// if we encounter the special case described above, update the name attribute
			if ( this.propertyName === 'name' ) {
				// replace actual name attribute
				this.pNode.name = '{{' + this.keypath + '}}';
			}
		},

		reassign: function ( indexRef, newIndex, oldKeypath, newKeypath ) {
			if ( this.fragment ) {
				this.fragment.reassign( indexRef, newIndex, oldKeypath, newKeypath );

				if ( this.twoway ) {
					this.updateBindings();
				}
			}
		},

		teardown: function () {
			var i;

			if ( this.boundEvents ) {
				i = this.boundEvents.length;

				while ( i-- ) {
					this.pNode.removeEventListener( this.boundEvents[i], this.updateModel, false );
				}
			}

			// ignore non-dynamic attributes
			if ( this.fragment ) {
				this.fragment.teardown();
			}
		},

		bubble: function () {
			// If an attribute's text fragment contains a single item, we can
			// update the DOM immediately...
			if ( this.selfUpdating ) {
				this.update();
			}

			// otherwise we want to register it as a deferred attribute, to be
			// updated once all the information is in, to prevent unnecessary
			// DOM manipulation
			else if ( !this.deferred && this.ready ) {
				runloop.addAttribute( this );
				this.deferred = true;
			}
		},

		toString: function () {
			var escaped, interpolator;

			if ( this.value === null ) {
				return this.name;
			}

			// Special case - select values (should not be stringified)
			if ( this.name === 'value' && this.element.lcName === 'select' ) {
				return;
			}

			// Special case - radio names
			if ( this.name === 'name' && this.element.lcName === 'input' && ( interpolator = this.interpolator ) ) {
				return 'name={{' + ( interpolator.keypath || interpolator.ref ) + '}}';
			}

			// Special case - boolean attributes
			if ( this.fragment && booleanAttributes.test( this.name ) ) {
				return this.fragment.getValue() ? this.name : null;
			}

			if ( this.fragment ) {
				escaped = escape( this.fragment.toString() );
			} else {
				escaped = escape( this.value );
			}

			return this.name + ( escaped ? ( '="' + escaped + '"' ) : '' );
		}
	};

	function escape ( string ) {
		return string
			.replace( /&/g, '&' )
			.replace( /"/g, '"' )
			.replace( /'/g, ''' );
	}

	return DomAttribute;

});
<|endoftext|>