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

  var CanvasRenderer = $$('renderer', 'canvas');

// Draw edge
  CanvasRenderer.prototype.drawEdge = function(context, edge, drawOverlayInstead) {
    var rs = edge._private.rscratch;
    var usePaths = CanvasRenderer.usePaths();

    // if bezier ctrl pts can not be calculated, then die
    if( rs.badBezier || (rs.edgeType === 'bezier' && isNaN(rs.startX)) ){ // extra isNaN() for safari 7.1 b/c it mangles ctrlpt calcs
      return;
    }

    var style = edge._private.style;

    // Edge line width
    if (style['width'].pxValue <= 0) {
      return;
    }

    var overlayPadding = style['overlay-padding'].pxValue;
    var overlayOpacity = style['overlay-opacity'].value;
    var overlayColor = style['overlay-color'].value;

    // Edge color & opacity
    if( drawOverlayInstead ){

      if( overlayOpacity === 0 ){ // exit early if no overlay
        return;
      }

      this.strokeStyle(context, overlayColor[0], overlayColor[1], overlayColor[2], overlayOpacity);
      context.lineCap = 'round';

      if( edge._private.rscratch.edgeType == 'self' && !usePaths ){
        context.lineCap = 'butt';
      }

    } else {
      var lineColor = style['line-color'].value;

      this.strokeStyle(context, lineColor[0], lineColor[1], lineColor[2], style.opacity.value);

      context.lineCap = 'butt';
    }

    var startNode, endNode, source, target;
    source = startNode = edge._private.source;
    target = endNode = edge._private.target;

    var targetPos = target._private.position;
    var targetW = target.width();
    var targetH = target.height();
    var sourcePos = source._private.position;
    var sourceW = source.width();
    var sourceH = source.height();

    var edgeWidth = style['width'].pxValue + (drawOverlayInstead ? 2 * overlayPadding : 0);
    var lineStyle = drawOverlayInstead ? 'solid' : style['line-style'].value;
    context.lineWidth = edgeWidth;

    if( rs.edgeType !== 'haystack' ){
      //this.findEndpoints(edge);
    }

    if( rs.edgeType === 'haystack' ){
      var radius = style['haystack-radius'].value;
      var halfRadius = radius/2; // b/c have to half width/height

      this.drawStyledEdge(
        edge,
        context,
        rs.haystackPts = [
          rs.source.x * sourceW * halfRadius + sourcePos.x,
          rs.source.y * sourceH * halfRadius + sourcePos.y,
          rs.target.x * targetW * halfRadius + targetPos.x,
          rs.target.y * targetH * halfRadius + targetPos.y
        ],
        lineStyle,
        edgeWidth
      );
    } else if (rs.edgeType === 'self') {

      var details = edge._private.rscratch;
      var points = [details.startX, details.startY, details.cp2ax,
        details.cp2ay, details.selfEdgeMidX, details.selfEdgeMidY,
        details.selfEdgeMidX, details.selfEdgeMidY,
        details.cp2cx, details.cp2cy, details.endX, details.endY];

      var details = edge._private.rscratch;
      this.drawStyledEdge(edge, context, points, lineStyle, edgeWidth);

    } else if (rs.edgeType === 'straight') {

      var nodeDirectionX = endNode._private.position.x - startNode._private.position.x;
      var nodeDirectionY = endNode._private.position.y - startNode._private.position.y;

      var edgeDirectionX = rs.endX - rs.startX;
      var edgeDirectionY = rs.endY - rs.startY;

      if (nodeDirectionX * edgeDirectionX
        + nodeDirectionY * edgeDirectionY < 0) {

        rs.straightEdgeTooShort = true;
      } else {

        var details = rs;
        this.drawStyledEdge(edge, context, [details.startX, details.startY,
                                      details.endX, details.endY],
                                      lineStyle,
                                      edgeWidth);

        rs.straightEdgeTooShort = false;
      }
    } else {

      var details = rs;

      this.drawStyledEdge(edge, context, [details.startX, details.startY,
        details.cp2x, details.cp2y, details.endX, details.endY],
        lineStyle,
        edgeWidth);

    }

    if( rs.edgeType === 'haystack' ){
      this.drawArrowheads(context, edge, drawOverlayInstead);
    } else if ( rs.noArrowPlacement !== true && rs.startX !== undefined ){
      this.drawArrowheads(context, edge, drawOverlayInstead);
    }

  };

  CanvasRenderer.prototype.drawStyledEdge = function(
      edge, context, pts, type, width) {

    // 3 points given -> assume Bezier
    // 2 -> assume straight

    var rs = edge._private.rscratch;
    var canvasCxt = context;
    var path;
    var pathCacheHit = false;
    var usePaths = CanvasRenderer.usePaths();

    if( usePaths ){

      var pathCacheKey = pts;
      var keyLengthMatches = rs.pathCacheKey && pathCacheKey.length === rs.pathCacheKey.length;
      var keyMatches = keyLengthMatches;

      for( var i = 0; keyMatches && i < pathCacheKey.length; i++ ){
        if( rs.pathCacheKey[i] !== pathCacheKey[i] ){
          keyMatches = false;
        }
      }

      if( keyMatches ){
        path = context = rs.pathCache;
        pathCacheHit = true;
      } else {
        path = context = new Path2D();
        rs.pathCacheKey = pathCacheKey;
        rs.pathCache = path;
      }

    }

    if( canvasCxt.setLineDash ){ // for very outofdate browsers
      switch( type ){
        case 'dotted':
          canvasCxt.setLineDash([ 1, 1 ]);
          break;

        case 'dashed':
          canvasCxt.setLineDash([ 6, 3 ]);
          break;

        case 'solid':
          canvasCxt.setLineDash([ ]);
          break;
      }
    }

    if( !pathCacheHit ){
      if( context.beginPath ){ context.beginPath(); }
      context.moveTo(pts[0], pts[1]);

      if (pts.length === 3 * 2) { // bezier
        context.quadraticCurveTo(pts[2], pts[3], pts[4], pts[5]);
      } else if( pts.length === 3 * 2 * 2 ){ // double bezier loop
        context.quadraticCurveTo(pts[2], pts[3], pts[4], pts[5]);
        context.quadraticCurveTo(pts[8], pts[9], pts[10], pts[11]);
      } else { // line
        context.lineTo(pts[2], pts[3]);
      }
    }

    context = canvasCxt;
    if( usePaths ){
      context.stroke( path );
    } else {
      context.stroke();
    }

    // reset any line dashes
    if( context.setLineDash ){ // for very outofdate browsers
      context.setLineDash([ ]);
    }

  };

  CanvasRenderer.prototype.drawArrowheads = function(context, edge, drawOverlayInstead) {
    if( drawOverlayInstead ){ return; } // don't do anything for overlays

    var rs = edge._private.rscratch;
    var self = this;
    var isHaystack = rs.edgeType === 'haystack';

    // Displacement gives direction for arrowhead orientation
    var dispX, dispY;
    var startX, startY, endX, endY;

    var srcPos = edge.source().position();
    var tgtPos = edge.target().position();

    if( isHaystack ){
      startX = rs.haystackPts[0];
      startY = rs.haystackPts[1];
      endX = rs.haystackPts[2];
      endY = rs.haystackPts[3];
    } else {
      startX = rs.arrowStartX;
      startY = rs.arrowStartY;
      endX = rs.arrowEndX;
      endY = rs.arrowEndY;
    }

    var style = edge._private.style;

    function drawArrowhead( prefix, x, y, dispX, dispY ){
      var arrowShape = style[prefix + '-arrow-shape'].value;

      if( arrowShape === 'none' ){
        return;
      }

      var gco = context.globalCompositeOperation;

      context.globalCompositeOperation = 'destination-out';

      self.fillStyle(context, 255, 255, 255, 1);

      var arrowClearFill = style[prefix + '-arrow-fill'].value === 'hollow' ? 'both' : 'filled';
      var arrowFill = style[prefix + '-arrow-fill'].value;

      if( arrowShape === 'half-triangle-overshot' ){
        arrowFill = 'hollow';
        arrowClearFill = 'hollow';
      }

      self.drawArrowShape( edge, prefix, context,
        arrowClearFill, style['width'].pxValue, style[prefix + '-arrow-shape'].value,
        x, y, dispX, dispY
      );

      context.globalCompositeOperation = gco;

      var color = style[prefix + '-arrow-color'].value;
      self.fillStyle(context, color[0], color[1], color[2], style.opacity.value);

      self.drawArrowShape( edge, prefix, context,
        arrowFill, style['width'].pxValue, style[prefix + '-arrow-shape'].value,
        x, y, dispX, dispY
      );
    }

    dispX = startX - srcPos.x;
    dispY = startY - srcPos.y;

    if( !isHaystack && !isNaN(startX) && !isNaN(startY) && !isNaN(dispX) && !isNaN(dispY) ){
      drawArrowhead( 'source', startX, startY, dispX, dispY );

    } else {
      // window.badArrow = true;
      // debugger;
    }

    var midX = rs.midX;
    var midY = rs.midY;

    if( isHaystack ){
      midX = ( startX + endX )/2;
      midY = ( startY + endY )/2;
    }

    dispX = startX - endX;
    dispY = startY - endY;

    if( rs.edgeType === 'self' ){
      dispX = 1;
      dispY = -1;
    }

    if( !isNaN(midX) && !isNaN(midY) ){
      drawArrowhead( 'mid-target', midX, midY, dispX, dispY );
    }

    dispX *= -1;
    dispY *= -1;

    if( !isNaN(midX) && !isNaN(midY) ){
      drawArrowhead( 'mid-source', midX, midY, dispX, dispY );
    }

    dispX = endX - tgtPos.x;
    dispY = endY - tgtPos.y;

    if( !isHaystack && !isNaN(endX) && !isNaN(endY) && !isNaN(dispX) && !isNaN(dispY) ){
      drawArrowhead( 'target', endX, endY, dispX, dispY );
    }
  };

  // Draw arrowshape
  CanvasRenderer.prototype.drawArrowShape = function(edge, arrowType, context, fill, edgeWidth, shape, x, y, dispX, dispY) {
    var usePaths = CanvasRenderer.usePaths();
    var rs = edge._private.rscratch;
    var pathCacheHit = false;
    var path;
    var canvasContext = context;
    var translation = { x: x, y: y };

    // Negative of the angle
    var angle = Math.asin(dispY / (Math.sqrt(dispX * dispX + dispY * dispY)));

    if (dispX < 0) {
      angle = angle + Math.PI / 2;
    } else {
      angle = - (Math.PI / 2 + angle);
    }

    var size = this.getArrowWidth( edgeWidth );
    var shapeImpl = CanvasRenderer.arrowShapes[shape];

    // context.translate(x, y);

    if( usePaths ){
      var pathCacheKey = size + '$' + shape + '$' + angle + '$' + x + '$' + y;
      rs.arrowPathCacheKey = rs.arrowPathCacheKey || {};
      rs.arrowPathCache = rs.arrowPathCache || {};

      var alreadyCached = rs.arrowPathCacheKey[arrowType] === pathCacheKey;
      if( alreadyCached ){
        path = context = rs.arrowPathCache[arrowType];
        pathCacheHit = true;
      } else {
        path = context = new Path2D();
        rs.arrowPathCacheKey[arrowType] = pathCacheKey;
        rs.arrowPathCache[arrowType] = path;
      }
    }

    if( context.beginPath ){ context.beginPath(); }

    if( !pathCacheHit ){
      shapeImpl.draw(context, size, angle, translation);
    }

    if( !shapeImpl.leavePathOpen && context.closePath ){
      context.closePath();
    }

    context = canvasContext;

    if( fill === 'filled' || fill === 'both' ){
      if( usePaths ){
        context.fill( path );
      } else {
        context.fill();
      }
    }

    if( fill === 'hollow' || fill === 'both' ){
      context.lineWidth = ( shapeImpl.matchEdgeWidth ? edgeWidth : 1 );
      context.lineJoin = 'miter';

      if( usePaths ){
        context.stroke( path );
      } else {
        context.stroke();
      }

    }

    // context.translate(-x, -y);
  };

})( cytoscape );<commit_msg>also apply safari workaround to straight rendered edges for #688<commit_after>;(function($$){ 'use strict';

  var CanvasRenderer = $$('renderer', 'canvas');

// Draw edge
  CanvasRenderer.prototype.drawEdge = function(context, edge, drawOverlayInstead) {
    var rs = edge._private.rscratch;
    var usePaths = CanvasRenderer.usePaths();

    // if bezier ctrl pts can not be calculated, then die
    if( rs.badBezier || ( (rs.edgeType === 'bezier' || rs.edgeType === 'straight') && isNaN(rs.startX)) ){ // extra isNaN() for safari 7.1 b/c it mangles ctrlpt calcs
      return;
    }

    var style = edge._private.style;

    // Edge line width
    if (style['width'].pxValue <= 0) {
      return;
    }

    var overlayPadding = style['overlay-padding'].pxValue;
    var overlayOpacity = style['overlay-opacity'].value;
    var overlayColor = style['overlay-color'].value;

    // Edge color & opacity
    if( drawOverlayInstead ){

      if( overlayOpacity === 0 ){ // exit early if no overlay
        return;
      }

      this.strokeStyle(context, overlayColor[0], overlayColor[1], overlayColor[2], overlayOpacity);
      context.lineCap = 'round';

      if( edge._private.rscratch.edgeType == 'self' && !usePaths ){
        context.lineCap = 'butt';
      }

    } else {
      var lineColor = style['line-color'].value;

      this.strokeStyle(context, lineColor[0], lineColor[1], lineColor[2], style.opacity.value);

      context.lineCap = 'butt';
    }

    var startNode, endNode, source, target;
    source = startNode = edge._private.source;
    target = endNode = edge._private.target;

    var targetPos = target._private.position;
    var targetW = target.width();
    var targetH = target.height();
    var sourcePos = source._private.position;
    var sourceW = source.width();
    var sourceH = source.height();

    var edgeWidth = style['width'].pxValue + (drawOverlayInstead ? 2 * overlayPadding : 0);
    var lineStyle = drawOverlayInstead ? 'solid' : style['line-style'].value;
    context.lineWidth = edgeWidth;

    if( rs.edgeType !== 'haystack' ){
      //this.findEndpoints(edge);
    }

    if( rs.edgeType === 'haystack' ){
      var radius = style['haystack-radius'].value;
      var halfRadius = radius/2; // b/c have to half width/height

      this.drawStyledEdge(
        edge,
        context,
        rs.haystackPts = [
          rs.source.x * sourceW * halfRadius + sourcePos.x,
          rs.source.y * sourceH * halfRadius + sourcePos.y,
          rs.target.x * targetW * halfRadius + targetPos.x,
          rs.target.y * targetH * halfRadius + targetPos.y
        ],
        lineStyle,
        edgeWidth
      );
    } else if (rs.edgeType === 'self') {

      var details = edge._private.rscratch;
      var points = [details.startX, details.startY, details.cp2ax,
        details.cp2ay, details.selfEdgeMidX, details.selfEdgeMidY,
        details.selfEdgeMidX, details.selfEdgeMidY,
        details.cp2cx, details.cp2cy, details.endX, details.endY];

      var details = edge._private.rscratch;
      this.drawStyledEdge(edge, context, points, lineStyle, edgeWidth);

    } else if (rs.edgeType === 'straight') {

      var nodeDirectionX = endNode._private.position.x - startNode._private.position.x;
      var nodeDirectionY = endNode._private.position.y - startNode._private.position.y;

      var edgeDirectionX = rs.endX - rs.startX;
      var edgeDirectionY = rs.endY - rs.startY;

      if (nodeDirectionX * edgeDirectionX
        + nodeDirectionY * edgeDirectionY < 0) {

        rs.straightEdgeTooShort = true;
      } else {

        var details = rs;
        this.drawStyledEdge(edge, context, [details.startX, details.startY,
                                      details.endX, details.endY],
                                      lineStyle,
                                      edgeWidth);

        rs.straightEdgeTooShort = false;
      }
    } else {

      var details = rs;

      this.drawStyledEdge(edge, context, [details.startX, details.startY,
        details.cp2x, details.cp2y, details.endX, details.endY],
        lineStyle,
        edgeWidth);

    }

    if( rs.edgeType === 'haystack' ){
      this.drawArrowheads(context, edge, drawOverlayInstead);
    } else if ( rs.noArrowPlacement !== true && rs.startX !== undefined ){
      this.drawArrowheads(context, edge, drawOverlayInstead);
    }

  };

  CanvasRenderer.prototype.drawStyledEdge = function(
      edge, context, pts, type, width) {

    // 3 points given -> assume Bezier
    // 2 -> assume straight

    var rs = edge._private.rscratch;
    var canvasCxt = context;
    var path;
    var pathCacheHit = false;
    var usePaths = CanvasRenderer.usePaths();

    if( usePaths ){

      var pathCacheKey = pts;
      var keyLengthMatches = rs.pathCacheKey && pathCacheKey.length === rs.pathCacheKey.length;
      var keyMatches = keyLengthMatches;

      for( var i = 0; keyMatches && i < pathCacheKey.length; i++ ){
        if( rs.pathCacheKey[i] !== pathCacheKey[i] ){
          keyMatches = false;
        }
      }

      if( keyMatches ){
        path = context = rs.pathCache;
        pathCacheHit = true;
      } else {
        path = context = new Path2D();
        rs.pathCacheKey = pathCacheKey;
        rs.pathCache = path;
      }

    }

    if( canvasCxt.setLineDash ){ // for very outofdate browsers
      switch( type ){
        case 'dotted':
          canvasCxt.setLineDash([ 1, 1 ]);
          break;

        case 'dashed':
          canvasCxt.setLineDash([ 6, 3 ]);
          break;

        case 'solid':
          canvasCxt.setLineDash([ ]);
          break;
      }
    }

    if( !pathCacheHit ){
      if( context.beginPath ){ context.beginPath(); }
      context.moveTo(pts[0], pts[1]);

      if (pts.length === 3 * 2) { // bezier
        context.quadraticCurveTo(pts[2], pts[3], pts[4], pts[5]);
      } else if( pts.length === 3 * 2 * 2 ){ // double bezier loop
        context.quadraticCurveTo(pts[2], pts[3], pts[4], pts[5]);
        context.quadraticCurveTo(pts[8], pts[9], pts[10], pts[11]);
      } else { // line
        context.lineTo(pts[2], pts[3]);
      }
    }

    context = canvasCxt;
    if( usePaths ){
      context.stroke( path );
    } else {
      context.stroke();
    }

    // reset any line dashes
    if( context.setLineDash ){ // for very outofdate browsers
      context.setLineDash([ ]);
    }

  };

  CanvasRenderer.prototype.drawArrowheads = function(context, edge, drawOverlayInstead) {
    if( drawOverlayInstead ){ return; } // don't do anything for overlays

    var rs = edge._private.rscratch;
    var self = this;
    var isHaystack = rs.edgeType === 'haystack';

    // Displacement gives direction for arrowhead orientation
    var dispX, dispY;
    var startX, startY, endX, endY;

    var srcPos = edge.source().position();
    var tgtPos = edge.target().position();

    if( isHaystack ){
      startX = rs.haystackPts[0];
      startY = rs.haystackPts[1];
      endX = rs.haystackPts[2];
      endY = rs.haystackPts[3];
    } else {
      startX = rs.arrowStartX;
      startY = rs.arrowStartY;
      endX = rs.arrowEndX;
      endY = rs.arrowEndY;
    }

    var style = edge._private.style;

    function drawArrowhead( prefix, x, y, dispX, dispY ){
      var arrowShape = style[prefix + '-arrow-shape'].value;

      if( arrowShape === 'none' ){
        return;
      }

      var gco = context.globalCompositeOperation;

      context.globalCompositeOperation = 'destination-out';

      self.fillStyle(context, 255, 255, 255, 1);

      var arrowClearFill = style[prefix + '-arrow-fill'].value === 'hollow' ? 'both' : 'filled';
      var arrowFill = style[prefix + '-arrow-fill'].value;

      if( arrowShape === 'half-triangle-overshot' ){
        arrowFill = 'hollow';
        arrowClearFill = 'hollow';
      }

      self.drawArrowShape( edge, prefix, context,
        arrowClearFill, style['width'].pxValue, style[prefix + '-arrow-shape'].value,
        x, y, dispX, dispY
      );

      context.globalCompositeOperation = gco;

      var color = style[prefix + '-arrow-color'].value;
      self.fillStyle(context, color[0], color[1], color[2], style.opacity.value);

      self.drawArrowShape( edge, prefix, context,
        arrowFill, style['width'].pxValue, style[prefix + '-arrow-shape'].value,
        x, y, dispX, dispY
      );
    }

    dispX = startX - srcPos.x;
    dispY = startY - srcPos.y;

    if( !isHaystack && !isNaN(startX) && !isNaN(startY) && !isNaN(dispX) && !isNaN(dispY) ){
      drawArrowhead( 'source', startX, startY, dispX, dispY );

    } else {
      // window.badArrow = true;
      // debugger;
    }

    var midX = rs.midX;
    var midY = rs.midY;

    if( isHaystack ){
      midX = ( startX + endX )/2;
      midY = ( startY + endY )/2;
    }

    dispX = startX - endX;
    dispY = startY - endY;

    if( rs.edgeType === 'self' ){
      dispX = 1;
      dispY = -1;
    }

    if( !isNaN(midX) && !isNaN(midY) ){
      drawArrowhead( 'mid-target', midX, midY, dispX, dispY );
    }

    dispX *= -1;
    dispY *= -1;

    if( !isNaN(midX) && !isNaN(midY) ){
      drawArrowhead( 'mid-source', midX, midY, dispX, dispY );
    }

    dispX = endX - tgtPos.x;
    dispY = endY - tgtPos.y;

    if( !isHaystack && !isNaN(endX) && !isNaN(endY) && !isNaN(dispX) && !isNaN(dispY) ){
      drawArrowhead( 'target', endX, endY, dispX, dispY );
    }
  };

  // Draw arrowshape
  CanvasRenderer.prototype.drawArrowShape = function(edge, arrowType, context, fill, edgeWidth, shape, x, y, dispX, dispY) {
    var usePaths = CanvasRenderer.usePaths();
    var rs = edge._private.rscratch;
    var pathCacheHit = false;
    var path;
    var canvasContext = context;
    var translation = { x: x, y: y };

    // Negative of the angle
    var angle = Math.asin(dispY / (Math.sqrt(dispX * dispX + dispY * dispY)));

    if (dispX < 0) {
      angle = angle + Math.PI / 2;
    } else {
      angle = - (Math.PI / 2 + angle);
    }

    var size = this.getArrowWidth( edgeWidth );
    var shapeImpl = CanvasRenderer.arrowShapes[shape];

    // context.translate(x, y);

    if( usePaths ){
      var pathCacheKey = size + '$' + shape + '$' + angle + '$' + x + '$' + y;
      rs.arrowPathCacheKey = rs.arrowPathCacheKey || {};
      rs.arrowPathCache = rs.arrowPathCache || {};

      var alreadyCached = rs.arrowPathCacheKey[arrowType] === pathCacheKey;
      if( alreadyCached ){
        path = context = rs.arrowPathCache[arrowType];
        pathCacheHit = true;
      } else {
        path = context = new Path2D();
        rs.arrowPathCacheKey[arrowType] = pathCacheKey;
        rs.arrowPathCache[arrowType] = path;
      }
    }

    if( context.beginPath ){ context.beginPath(); }

    if( !pathCacheHit ){
      shapeImpl.draw(context, size, angle, translation);
    }

    if( !shapeImpl.leavePathOpen && context.closePath ){
      context.closePath();
    }

    context = canvasContext;

    if( fill === 'filled' || fill === 'both' ){
      if( usePaths ){
        context.fill( path );
      } else {
        context.fill();
      }
    }

    if( fill === 'hollow' || fill === 'both' ){
      context.lineWidth = ( shapeImpl.matchEdgeWidth ? edgeWidth : 1 );
      context.lineJoin = 'miter';

      if( usePaths ){
        context.stroke( path );
      } else {
        context.stroke();
      }

    }

    // context.translate(-x, -y);
  };

})( cytoscape );<|endoftext|>


<commit_before>var services = require('../core/services'),
    _ = require('lodash');

services.factory('pluginService', ['$rootScope', '$q', 'errorService', pluginService]);

function pluginService ($rootScope, $q, errorService) {

    var loadedCallbacks = [],
        wrapper = {
            isLoaded: false,
            onLoaded: onLoaded
        },
        errors = {};

    $rootScope.$watch('plugin', function (plugin) {

        if (!plugin) {
            return;
        }

        wrapper.isLoaded = true;

        errors[plugin.errorCodes.UNKNOWN_ERROR] = 'Неизвестная ошибка';
        errors[plugin.errorCodes.BAD_PARAMS] = 'Неправильные параметры';
        errors[plugin.errorCodes.DEVICE_NOT_FOUND] = 'Устройство не найдено';
        errors[plugin.errorCodes.CERTIFICATE_CATEGORY_BAD] = 'Недопустимый тип сертификата';
        errors[plugin.errorCodes.CERTIFICATE_EXISTS] = 'Сертификат уже существует на устройстве';
        errors[plugin.errorCodes.PKCS11_LOAD_FAILED] = 'Не удалось загрузить PKCS#11 библиотеку';
        errors[plugin.errorCodes.NOT_ENOUGH_MEMORY] = 'Недостаточно памяти';
        errors[plugin.errorCodes.PIN_LENGTH_INVALID] = 'Некорректная длина PIN-кода';
        errors[plugin.errorCodes.PIN_INCORRECT] = 'Некорректный PIN-код';
        errors[plugin.errorCodes.PIN_LOCKED] = 'PIN-код заблокирован';
        errors[plugin.errorCodes.PIN_CHANGED] = 'PIN-код был изменен';
        errors[plugin.errorCodes.SESSION_INVALID] = 'Состояние токена изменилось';
        errors[plugin.errorCodes.USER_NOT_LOGGED_IN] = 'Выполните вход на устройство';
        errors[plugin.errorCodes.KEY_NOT_FOUND] = 'Соответствующая сертификату ключевая пара не найдена';
        errors[plugin.errorCodes.KEY_ID_NOT_UNIQUE] = 'Идентификатор ключевой пары не уникален';
        errors[plugin.errorCodes.CERTIFICATE_NOT_FOUND] = 'Сертификат не найден';
        errors[plugin.errorCodes.CERTIFICATE_HASH_NOT_UNIQUE] = 'Хэш сертификата не уникален';
        errors[plugin.errorCodes.TOKEN_INVALID] = 'Ошибка чтения/записи устройства. Возможно, устройство было извлечено. Попробуйте выполнить enumerate';
        errors[plugin.errorCodes.BASE64_DECODE_FAILED] = 'Ошибка декодирования даных из BASE64';
        errors[plugin.errorCodes.PEM_ERROR] = 'Ошибка разбора PEM';
        errors[plugin.errorCodes.ASN1_ERROR] = 'Ошибка декодирования ASN1 структуры';
        errors[plugin.errorCodes.WRONG_KEY_TYPE] = 'Неправильный тип ключа';

        _.each(plugin, function (value , key) {

            if (_.isNumber(value)) {
                wrapper[key] = value;
                return;
            // } else if (!_.isFunction(value)) {
            //     return;
            // }

            } else if(typeof value != 'object'){
                return
            }

            wrapper[key] = function () {

                var defer = $q.defer(),
                    args = [].slice.call(arguments).concat(resultCallback, errorCallback);

                plugin[key].apply(plugin, args);

                return defer.promise;

                function resultCallback (result) {
                    defer.resolve(result);
                    $rootScope.$apply();
                }

                function errorCallback (code, text) {
                    errorService.show(errors[code]);
                    defer.reject(code);
                    $rootScope.$apply();
                }
            };
        });

        _.each(loadedCallbacks, function(callback) {
            callback();
        });
    });

    function onLoaded(callback) {

        loadedCallbacks.push(callback);

        if (wrapper.isLoaded) {
            callback();
        }
    }

    return wrapper;
}

window.onPluginLoaded = function(plugin) {

    var root = angular.element(document.body).scope().$root;
    root.plugin = plugin;
    root.$apply();
};
<commit_msg>Fix pluginService on firefox<commit_after>var services = require('../core/services'),
    _ = require('lodash');

services.factory('pluginService', ['$rootScope', '$q', 'errorService', pluginService]);

function pluginService ($rootScope, $q, errorService) {

    var loadedCallbacks = [],
        wrapper = {
            isLoaded: false,
            onLoaded: onLoaded
        },
        errors = {};

    $rootScope.$watch('plugin', function (plugin) {

        if (!plugin) {
            return;
        }

        wrapper.isLoaded = true;

        errors[plugin.errorCodes.UNKNOWN_ERROR] = 'Неизвестная ошибка';
        errors[plugin.errorCodes.BAD_PARAMS] = 'Неправильные параметры';
        errors[plugin.errorCodes.DEVICE_NOT_FOUND] = 'Устройство не найдено';
        errors[plugin.errorCodes.CERTIFICATE_CATEGORY_BAD] = 'Недопустимый тип сертификата';
        errors[plugin.errorCodes.CERTIFICATE_EXISTS] = 'Сертификат уже существует на устройстве';
        errors[plugin.errorCodes.PKCS11_LOAD_FAILED] = 'Не удалось загрузить PKCS#11 библиотеку';
        errors[plugin.errorCodes.NOT_ENOUGH_MEMORY] = 'Недостаточно памяти';
        errors[plugin.errorCodes.PIN_LENGTH_INVALID] = 'Некорректная длина PIN-кода';
        errors[plugin.errorCodes.PIN_INCORRECT] = 'Некорректный PIN-код';
        errors[plugin.errorCodes.PIN_LOCKED] = 'PIN-код заблокирован';
        errors[plugin.errorCodes.PIN_CHANGED] = 'PIN-код был изменен';
        errors[plugin.errorCodes.SESSION_INVALID] = 'Состояние токена изменилось';
        errors[plugin.errorCodes.USER_NOT_LOGGED_IN] = 'Выполните вход на устройство';
        errors[plugin.errorCodes.KEY_NOT_FOUND] = 'Соответствующая сертификату ключевая пара не найдена';
        errors[plugin.errorCodes.KEY_ID_NOT_UNIQUE] = 'Идентификатор ключевой пары не уникален';
        errors[plugin.errorCodes.CERTIFICATE_NOT_FOUND] = 'Сертификат не найден';
        errors[plugin.errorCodes.CERTIFICATE_HASH_NOT_UNIQUE] = 'Хэш сертификата не уникален';
        errors[plugin.errorCodes.TOKEN_INVALID] = 'Ошибка чтения/записи устройства. Возможно, устройство было извлечено. Попробуйте выполнить enumerate';
        errors[plugin.errorCodes.BASE64_DECODE_FAILED] = 'Ошибка декодирования даных из BASE64';
        errors[plugin.errorCodes.PEM_ERROR] = 'Ошибка разбора PEM';
        errors[plugin.errorCodes.ASN1_ERROR] = 'Ошибка декодирования ASN1 структуры';
        errors[plugin.errorCodes.WRONG_KEY_TYPE] = 'Неправильный тип ключа';

        _.each(plugin, function (value , key) {

            if (_.isNumber(value)) {
                wrapper[key] = value;
                return;
            // } else if (!_.isFunction(value)) {
            //     return;
            // }

            } else if(typeof value != 'object' && typeof value != 'function'){
                return
            }

            wrapper[key] = function () {

                var defer = $q.defer(),
                    args = [].slice.call(arguments).concat(resultCallback, errorCallback);

                plugin[key].apply(plugin, args);

                return defer.promise;

                function resultCallback (result) {
                    defer.resolve(result);
                    $rootScope.$apply();
                }

                function errorCallback (code, text) {
                    errorService.show(errors[code]);
                    defer.reject(code);
                    $rootScope.$apply();
                }
            };
        });

        _.each(loadedCallbacks, function(callback) {
            callback();
        });
    });

    function onLoaded(callback) {

        loadedCallbacks.push(callback);

        if (wrapper.isLoaded) {
            callback();
        }
    }

    return wrapper;
}

window.onPluginLoaded = function(plugin) {

    var root = angular.element(document.body).scope().$root;
    root.plugin = plugin;
    root.$apply();
};
<|endoftext|>


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

var express = require('express');
var api = require('lib/db-api');
var utils = require('lib/utils');
var accepts = require('lib/accepts');
var restrict = utils.restrict;
var staff = utils.staff;
var pluck = utils.pluck;
var expose = utils.expose;
var log = require('debug')('democracyos:law');
var config = require('lib/config');
var utils = require('lib/utils');
var notifier = require('lib/notifications');

var app = module.exports = express();

/**
 * Limit request to json format only
 */

app.use(accepts('application/json'));

app.get('/law/all', function (req, res) {
  log('Request /law/all');

  api.law.all(function(err, lawDocs) {
    if (err) return _handleError(err, req, res);

    // filter only public documents
    lawDocs = lawDocs.filter(function(law) {
      return law.public || (req.user && req.user.staff)
    });

    // add voted flag to returning documents
    lawDocs.forEach(function(law) {
      if (!req.user) return law.voted = false;
      law.voted = law.votedBy(req.user);
    });

    log('Serving laws %j', pluck(lawDocs, 'id'));

    var keys = [
      'id lawId mediaTitle status open closed public draft deleted',
      'tag participants voted createdAt updatedAt closingAt',
      'publishedAt deletedAt votable clauseTruncationText links author authorUrl'
    ].join(' ');

    res.json(lawDocs.map(expose(keys)));
  });
});

app.get('/law/:id', function (req, res) {
  log('Request GET /law/%s', req.params.id);

  api.law.get(req.params.id, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);
    if (!lawDoc) return res.send(404);

    log('Serving law %s', lawDoc.id);
    var keys = [
      'id lawId title mediaTitle summary clauses source status open closed public draft deleted',
      'satus state tag participants',
      'upvotes downvotes abstentions createdAt updatedAt closingAt',
      'publishedAt deletedAt votable clauseTruncationText links author authorUrl'
    ].join(' ');

    res.json(expose(keys)(lawDoc.toJSON()));
  });
});

app.get('/law/:id/summary-comments', function (req, res) {
  log('Request /law/%s/summary-comments', req.params.id);

  api.law.get(req.params.id, function (err) {
    if (err) return _handleError(err, req, res);

    var re = new RegExp(req.params.id + '\-\\d');
    var paging = { page: 0, limit: 0, sort: 'score', exclude_user: null };
    api.comment.getFor({context: 'summary', reference: re}, paging, function (err, comments) {
      if (err) return _handleError(err, req, res);

      log('Serving law %s summary\'s comments %j', req.params.id, pluck(comments, 'id'));

      var keys = [
        'id text createdAt editedAt context reference',
        'author.id author.fullName author.avatar',
        'flags upvotes downvotes votes replies.length'
      ].join(' ');

      res.json(comments.map(expose(keys)));
    });

  });
});

app.get('/law/:id/clause-comments', function (req, res) {
  log('Request /law/%s/clause-comments', req.params.id);

  api.law.get(req.params.id, function (err, law) {
    if (err) return _handleError(err, req, res);

    var clauses = law.clauses.map(function (clause) { return clause.id });
    var paging = { page: 0, limit: 0, sort: 'score', exclude_user: null };
    api.comment.getFor({context: 'clause', reference: { $in: clauses } }, paging, function (err, comments) {
      if (err) return _handleError(err, req, res);

      log('Serving law %s clauses\' comments %j', req.params.id, pluck(comments, 'id'));

      var keys = [
        'id text createdAt editedAt context reference',
        'author.id author.fullName author.avatar',
        'flags upvotes downvotes votes replies.length'
      ].join(' ');

      res.json(comments.map(expose(keys)));
    });

  });
});

app.get('/law/:id/comments', function (req, res) {
  log('Request /law/%s/comments', req.params.id);

  var sort = '';
  if (~['-score', '-createdAt', 'createdAt'].indexOf(req.query.sort)) {
    sort = req.query.sort;
  } else {
    sort = '-score';
  }

  var paging = {
    page: req.query.page || 0,
    limit: req.query.limit || 0,
    sort: sort,
    exclude_user: req.query.exclude_user || null
  };

  api.law.comments(req.params.id, paging, function (err, comments) {
    if (err) return _handleError(err, req, res);

    if (!req.query.count) {
      log('Serving law %s comments %j', req.params.id, pluck(comments, 'id'));

      var keys = [
        "id text createdAt editedAt context reference",
        "author.id author.fullName author.avatar",
        "flags upvotes downvotes votes replies.length kind score"
      ].join(' ');

      res.json(comments.map(expose(keys)));
    } else {
      log('Serving law %s comments count: %d', req.params.id, comments.length);

      res.json(comments.length);
    }
  });
});

app.get('/law/:id/my-comments', restrict, function (req, res) {
  log('Request /law/%s/my-comments', req.params.id);

  api.law.userComments(req.params.id, req.user.id, function (err, comments) {
    if (err) return _handleError(err, req, res);

    log('Serving law %s comments %j for user %s', req.params.id, pluck(comments, 'id'), req.user.id);

    var keys = [
      "id text createdAt editedAt context reference",
      "author.id author.fullName author.avatar",
      "flags upvotes downvotes votes replies.length kind score"
    ].join(' ');

    res.json(comments.map(expose(keys)));
  });
});

app.post('/law/:id/comment', restrict, function (req, res) {
  log('Request /law/%s/comment %j', req.params.id, req.body.comment);

  var comment = {};

  // FIXME: data-structure should be the same for side and regular comments
  // I think the faulty approach is with regular comments - gvilarino 1/16/2015
  if (req.body.comment) {
    // side-comments
    comment = req.body.comment;
  }
  else {
    // regular comments
    comment.text = req.body.text;
    comment.context = 'law';
    comment.reference = req.params.id;
  }

  // This must be uncommented on production!
  comment.author = req.user.id;
  comment.kind = req.body.kind;

  api.law.comment(comment, function (err, commentDoc) {
    if (err) return _handleError(err, req, res);

    if (notifier.enabled()) {
      var eventName = 'law-commented';

      var instanceUrl = utils.buildUrl(config);

      notifier.notify(eventName)
        .withData( { law: req.params.id, user: req.user.id, instance: instanceUrl, comment: commentDoc.text } )
        .send(function (err, data) {
          if (err) {
            log('Error when sending notification for event %s', eventName);
          } else {
            log('Successfully notified voting of law %s', req.params.id);
          }
        });
    }

    var keys = [
      'id text',
      'author.id author.fullName author.avatar',
      'upvotes downvotes flags',
      'createdAt replies context reference'
    ].join(' ');

    res.json(200, expose(keys)(commentDoc));
  });
});

app.post('/law/:id/vote', restrict, function (req, res) {
  log('Request /law/%s/vote', req.param('id'));

  api.law
  .vote(
    req.param('id'),
    req.user,
    req.param('value'),
    function (err, law) {
      if (err) return _handleError(err, req, res);

      if (notifier.enabled()) {
        var eventName = 'law-voted';

        var instanceUrl = utils.buildUrl(config);
        var lawUrl = utils.buildUrl(config, { pathname: '/law/' + law.id });

        notifier.notify(eventName)
          .withData( { law: law.id, user: req.user.id, instance: instanceUrl, vote: req.param('value') } )
          .send(function (err, data) {
            if (err) {
              log('Error when sending notification for event %s', eventName);
            } else {
              log('Successfully notified voting of law %s', law.id);
            }
          });
      }

      log('Serving 200 OK response');
      res.send(200);
    }
  );
});

app.post('/law/create', restrict, /*staff,*/ function (req, res, next) {
  log('Request /law/create %j', req.body.law);

  api.law.create(req.body, function (err, lawDoc) {
    if (err) return next(err);
    var keys = [
      'id lawId title mediaTitle summary clauses source state',
      'status open closed public draft deleted tag participants',
      'upvotes downvotes abstentions createdAt updatedAt closingAt',
      'publishedAt deletedAt votable clauseTruncationText links author authorUrl'
    ].join(' ');
    res.json(expose(keys)(lawDoc));
  });
});

app.post('/law/:id', restrict, staff, function (req, res) {
  log('Request POST /law/%s', req.params.id);

  api.law.update(req.params.id, req.body, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);

    log('Serving law %s', lawDoc.id);
    var keys = [
      'id lawId title mediaTitle summary clauses source state',
      'status open closed public draft deleted tag participants',
      'upvotes downvotes abstentions createdAt updatedAt closingAt',
      'publishedAt deletedAt votable clauseTruncationText links author authorUrl'
    ].join(' ');

    res.json(expose(keys)(lawDoc.toJSON()));
  });
});

app.post('/law/:id/clause', restrict, staff, function (req, res) {
  log('Request POST /law/%s/clause', req.params.id);

  var clause = req.body.clause;
  api.law.get(req.params.id, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);

    var clauseDoc = clause && clause.id
      ? lawDoc.clauses.id(clause.id)
      : lawDoc.clauses.create();

    clauseDoc.update(clause);
    if (clauseDoc.isNew) lawDoc.clauses.push(clauseDoc);

    lawDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);

      res.json(200, expose('id order clauseName text centered')(clauseDoc));
    });
  });
});

app.del('/law/:id/clause', restrict, staff, function (req, res) {
  log('Request DELETE /law/%s/clause', req.params.id);

  var clause = req.body.clause;
  api.law.get(req.params.id, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);

    lawDoc.clauses.remove(clause);
    log('removed clause %s from law %s', clause, lawDoc.id);

    lawDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);

      res.json(200);
    });
  });
});

app.post('/law/:id/link', restrict, staff, function (req, res) {
  log('Request POST /law/%s/link', req.params.id);

  var link = req.body.link;
  api.law.get(req.params.id, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);

    var linkDoc = link && link.id
      ? lawDoc.links.id(link.id)
      : lawDoc.links.create();

    linkDoc.update(link);
    if (linkDoc.isNew) lawDoc.links.push(linkDoc);

    lawDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);

      res.json(200, expose('id text url')(linkDoc));
    });
  });
});

app.del('/law/:id/link', restrict, staff, function (req, res) {
  log('Request DELETE /law/%s/link', req.params.id);

  var link = req.body.link;
  api.law.get(req.params.id, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);

    lawDoc.links.remove(link);
    log('removed link %s from law %s', link, lawDoc.id);

    lawDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);

      res.json(200);
    });
  });
});

app.post('/law/:id/publish', restrict, staff, function (req, res) {
  log('Request POST /law/%s/publish', req.params.id);

  api.law.get(req.params.id, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);

    lawDoc.publishedAt = new Date;
    lawDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);
      log('publish law %s at %s', lawDoc.id, lawDoc.publishedAt);

      if (notifier.enabled()) {
        var eventName = 'law-published';

        var instanceUrl = utils.buildUrl(config);
        var lawUrl = utils.buildUrl(config, { pathname: '/law/' + lawDoc.id });

        var data = {
          law: { mediaTitle: lawDoc.mediaTitle, id: lawDoc.id },
          url: lawUrl,
          instance: instanceUrl
        };

        if (config.deploymentId) {
          data.deploymentId = config.deploymentId;
        }

        notifier.notify(eventName)
          .withData(data)
          .send(function (err) {
            if (err) {
              log('Error when sending notification for event %s', eventName);
            } else {
              log('Successfully notified publishing of law %s', lawDoc.id);
            }
          });
      }

      res.json(200);

    });
  });
});

app.post('/law/:id/unpublish', restrict, staff, function (req, res) {
  log('Request POST /law/%s/unpublish', req.params.id);

  api.law.get(req.params.id, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);

    lawDoc.publishedAt = null;
    lawDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);
      log('unpublished law %s', lawDoc.id);
      res.json(200);
    });
  });
});

app.post('/law/:id/delete', restrict, staff, function (req, res) {
  log('Request POST /law/%s/delete', req.params.id);

  api.law.get(req.params.id, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);

    lawDoc.deletedAt = new Date;
    lawDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);
      log('deleted law %s', lawDoc.id);
      res.json(200);
    });
  });
});

function _handleError (err, req, res) {
  log("Error found: %s", err);
  var error = err;
  if (err.errors && err.errors.text) error = err.errors.text;
  if (error.type) error = error.type;

  res.json(400, { error: error });
}
<commit_msg>[comments] Added official attribute<commit_after>/**
 * Module dependencies.
 */

var express = require('express');
var api = require('lib/db-api');
var utils = require('lib/utils');
var accepts = require('lib/accepts');
var restrict = utils.restrict;
var staff = utils.staff;
var pluck = utils.pluck;
var expose = utils.expose;
var log = require('debug')('democracyos:law');
var config = require('lib/config');
var utils = require('lib/utils');
var notifier = require('lib/notifications');

var app = module.exports = express();

/**
 * Limit request to json format only
 */

app.use(accepts('application/json'));

app.get('/law/all', function (req, res) {
  log('Request /law/all');

  api.law.all(function(err, lawDocs) {
    if (err) return _handleError(err, req, res);

    // filter only public documents
    lawDocs = lawDocs.filter(function(law) {
      return law.public || (req.user && req.user.staff)
    });

    // add voted flag to returning documents
    lawDocs.forEach(function(law) {
      if (!req.user) return law.voted = false;
      law.voted = law.votedBy(req.user);
    });

    log('Serving laws %j', pluck(lawDocs, 'id'));

    var keys = [
      'id lawId mediaTitle status open closed public draft deleted',
      'tag participants voted createdAt updatedAt closingAt',
      'publishedAt deletedAt votable clauseTruncationText links author authorUrl'
    ].join(' ');

    res.json(lawDocs.map(expose(keys)));
  });
});

app.get('/law/:id', function (req, res) {
  log('Request GET /law/%s', req.params.id);

  api.law.get(req.params.id, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);
    if (!lawDoc) return res.send(404);

    log('Serving law %s', lawDoc.id);
    var keys = [
      'id lawId title mediaTitle summary clauses source status open closed public draft deleted',
      'satus state tag participants',
      'upvotes downvotes abstentions createdAt updatedAt closingAt',
      'publishedAt deletedAt votable clauseTruncationText links author authorUrl'
    ].join(' ');

    res.json(expose(keys)(lawDoc.toJSON()));
  });
});

app.get('/law/:id/summary-comments', function (req, res) {
  log('Request /law/%s/summary-comments', req.params.id);

  api.law.get(req.params.id, function (err) {
    if (err) return _handleError(err, req, res);

    var re = new RegExp(req.params.id + '\-\\d');
    var paging = { page: 0, limit: 0, sort: 'score', exclude_user: null };
    api.comment.getFor({context: 'summary', reference: re}, paging, function (err, comments) {
      if (err) return _handleError(err, req, res);

      log('Serving law %s summary\'s comments %j', req.params.id, pluck(comments, 'id'));

      var keys = [
        'id text createdAt editedAt context reference',
        'author.id author.fullName author.avatar author.official',
        'flags upvotes downvotes votes replies.length'
      ].join(' ');

      res.json(comments.map(expose(keys)));
    });

  });
});

app.get('/law/:id/clause-comments', function (req, res) {
  log('Request /law/%s/clause-comments', req.params.id);

  api.law.get(req.params.id, function (err, law) {
    if (err) return _handleError(err, req, res);

    var clauses = law.clauses.map(function (clause) { return clause.id });
    var paging = { page: 0, limit: 0, sort: 'score', exclude_user: null };
    api.comment.getFor({context: 'clause', reference: { $in: clauses } }, paging, function (err, comments) {
      if (err) return _handleError(err, req, res);

      log('Serving law %s clauses\' comments %j', req.params.id, pluck(comments, 'id'));

      var keys = [
        'id text createdAt editedAt context reference',
        'author.id author.fullName author.avatar author.official',
        'flags upvotes downvotes votes replies.length'
      ].join(' ');

      res.json(comments.map(expose(keys)));
    });

  });
});

app.get('/law/:id/comments', function (req, res) {
  log('Request /law/%s/comments', req.params.id);

  var sort = '';
  if (~['-score', '-createdAt', 'createdAt'].indexOf(req.query.sort)) {
    sort = req.query.sort;
  } else {
    sort = '-score';
  }

  var paging = {
    page: req.query.page || 0,
    limit: req.query.limit || 0,
    sort: sort,
    exclude_user: req.query.exclude_user || null
  };

  api.law.comments(req.params.id, paging, function (err, comments) {
    if (err) return _handleError(err, req, res);

    if (!req.query.count) {
      log('Serving law %s comments %j', req.params.id, pluck(comments, 'id'));

      var keys = [
        "id text createdAt editedAt context reference",
        "author.id author.fullName author.avatar author.official",
        "flags upvotes downvotes votes replies.length kind score"
      ].join(' ');

      res.json(comments.map(expose(keys)));
    } else {
      log('Serving law %s comments count: %d', req.params.id, comments.length);

      res.json(comments.length);
    }
  });
});

app.get('/law/:id/my-comments', restrict, function (req, res) {
  log('Request /law/%s/my-comments', req.params.id);

  api.law.userComments(req.params.id, req.user.id, function (err, comments) {
    if (err) return _handleError(err, req, res);

    log('Serving law %s comments %j for user %s', req.params.id, pluck(comments, 'id'), req.user.id);

    var keys = [
      "id text createdAt editedAt context reference",
      "author.id author.fullName author.avatar author.official",
      "flags upvotes downvotes votes replies.length kind score"
    ].join(' ');

    res.json(comments.map(expose(keys)));
  });
});

app.post('/law/:id/comment', restrict, function (req, res) {
  log('Request /law/%s/comment %j', req.params.id, req.body.comment);

  var comment = {};

  // FIXME: data-structure should be the same for side and regular comments
  // I think the faulty approach is with regular comments - gvilarino 1/16/2015
  if (req.body.comment) {
    // side-comments
    comment = req.body.comment;
  }
  else {
    // regular comments
    comment.text = req.body.text;
    comment.context = 'law';
    comment.reference = req.params.id;
  }

  // This must be uncommented on production!
  comment.author = req.user.id;
  comment.kind = req.body.kind;

  api.law.comment(comment, function (err, commentDoc) {
    if (err) return _handleError(err, req, res);

    if (notifier.enabled()) {
      var eventName = 'law-commented';

      var instanceUrl = utils.buildUrl(config);

      notifier.notify(eventName)
        .withData( { law: req.params.id, user: req.user.id, instance: instanceUrl, comment: commentDoc.text } )
        .send(function (err, data) {
          if (err) {
            log('Error when sending notification for event %s', eventName);
          } else {
            log('Successfully notified voting of law %s', req.params.id);
          }
        });
    }

    var keys = [
      'id text',
      'author.id author.fullName author.avatar author.official',
      'upvotes downvotes flags',
      'createdAt replies context reference'
    ].join(' ');

    res.json(200, expose(keys)(commentDoc));
  });
});

app.post('/law/:id/vote', restrict, function (req, res) {
  log('Request /law/%s/vote', req.param('id'));

  api.law
  .vote(
    req.param('id'),
    req.user,
    req.param('value'),
    function (err, law) {
      if (err) return _handleError(err, req, res);

      if (notifier.enabled()) {
        var eventName = 'law-voted';

        var instanceUrl = utils.buildUrl(config);
        var lawUrl = utils.buildUrl(config, { pathname: '/law/' + law.id });

        notifier.notify(eventName)
          .withData( { law: law.id, user: req.user.id, instance: instanceUrl, vote: req.param('value') } )
          .send(function (err, data) {
            if (err) {
              log('Error when sending notification for event %s', eventName);
            } else {
              log('Successfully notified voting of law %s', law.id);
            }
          });
      }

      log('Serving 200 OK response');
      res.send(200);
    }
  );
});

app.post('/law/create', restrict, /*staff,*/ function (req, res, next) {
  log('Request /law/create %j', req.body.law);

  api.law.create(req.body, function (err, lawDoc) {
    if (err) return next(err);
    var keys = [
      'id lawId title mediaTitle summary clauses source state',
      'status open closed public draft deleted tag participants',
      'upvotes downvotes abstentions createdAt updatedAt closingAt',
      'publishedAt deletedAt votable clauseTruncationText links author authorUrl'
    ].join(' ');
    res.json(expose(keys)(lawDoc));
  });
});

app.post('/law/:id', restrict, staff, function (req, res) {
  log('Request POST /law/%s', req.params.id);

  api.law.update(req.params.id, req.body, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);

    log('Serving law %s', lawDoc.id);
    var keys = [
      'id lawId title mediaTitle summary clauses source state',
      'status open closed public draft deleted tag participants',
      'upvotes downvotes abstentions createdAt updatedAt closingAt',
      'publishedAt deletedAt votable clauseTruncationText links author authorUrl'
    ].join(' ');

    res.json(expose(keys)(lawDoc.toJSON()));
  });
});

app.post('/law/:id/clause', restrict, staff, function (req, res) {
  log('Request POST /law/%s/clause', req.params.id);

  var clause = req.body.clause;
  api.law.get(req.params.id, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);

    var clauseDoc = clause && clause.id
      ? lawDoc.clauses.id(clause.id)
      : lawDoc.clauses.create();

    clauseDoc.update(clause);
    if (clauseDoc.isNew) lawDoc.clauses.push(clauseDoc);

    lawDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);

      res.json(200, expose('id order clauseName text centered')(clauseDoc));
    });
  });
});

app.del('/law/:id/clause', restrict, staff, function (req, res) {
  log('Request DELETE /law/%s/clause', req.params.id);

  var clause = req.body.clause;
  api.law.get(req.params.id, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);

    lawDoc.clauses.remove(clause);
    log('removed clause %s from law %s', clause, lawDoc.id);

    lawDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);

      res.json(200);
    });
  });
});

app.post('/law/:id/link', restrict, staff, function (req, res) {
  log('Request POST /law/%s/link', req.params.id);

  var link = req.body.link;
  api.law.get(req.params.id, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);

    var linkDoc = link && link.id
      ? lawDoc.links.id(link.id)
      : lawDoc.links.create();

    linkDoc.update(link);
    if (linkDoc.isNew) lawDoc.links.push(linkDoc);

    lawDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);

      res.json(200, expose('id text url')(linkDoc));
    });
  });
});

app.del('/law/:id/link', restrict, staff, function (req, res) {
  log('Request DELETE /law/%s/link', req.params.id);

  var link = req.body.link;
  api.law.get(req.params.id, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);

    lawDoc.links.remove(link);
    log('removed link %s from law %s', link, lawDoc.id);

    lawDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);

      res.json(200);
    });
  });
});

app.post('/law/:id/publish', restrict, staff, function (req, res) {
  log('Request POST /law/%s/publish', req.params.id);

  api.law.get(req.params.id, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);

    lawDoc.publishedAt = new Date;
    lawDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);
      log('publish law %s at %s', lawDoc.id, lawDoc.publishedAt);

      if (notifier.enabled()) {
        var eventName = 'law-published';

        var instanceUrl = utils.buildUrl(config);
        var lawUrl = utils.buildUrl(config, { pathname: '/law/' + lawDoc.id });

        var data = {
          law: { mediaTitle: lawDoc.mediaTitle, id: lawDoc.id },
          url: lawUrl,
          instance: instanceUrl
        };

        if (config.deploymentId) {
          data.deploymentId = config.deploymentId;
        }

        notifier.notify(eventName)
          .withData(data)
          .send(function (err) {
            if (err) {
              log('Error when sending notification for event %s', eventName);
            } else {
              log('Successfully notified publishing of law %s', lawDoc.id);
            }
          });
      }

      res.json(200);

    });
  });
});

app.post('/law/:id/unpublish', restrict, staff, function (req, res) {
  log('Request POST /law/%s/unpublish', req.params.id);

  api.law.get(req.params.id, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);

    lawDoc.publishedAt = null;
    lawDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);
      log('unpublished law %s', lawDoc.id);
      res.json(200);
    });
  });
});

app.post('/law/:id/delete', restrict, staff, function (req, res) {
  log('Request POST /law/%s/delete', req.params.id);

  api.law.get(req.params.id, function (err, lawDoc) {
    if (err) return _handleError(err, req, res);

    lawDoc.deletedAt = new Date;
    lawDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);
      log('deleted law %s', lawDoc.id);
      res.json(200);
    });
  });
});

function _handleError (err, req, res) {
  log("Error found: %s", err);
  var error = err;
  if (err.errors && err.errors.text) error = err.errors.text;
  if (error.type) error = error.type;

  res.json(400, { error: error });
}
<|endoftext|>


<commit_before>"use strict";

var events = require("events");
var util = require("util");

/***** Private *****/

function isEmpty(obj) {
	for ( var key in obj) {
		if (obj.hasOwnProperty(key)) {
			return false;
		}
	}
	return true;
}

function clone(source) {
	var newClone = {};
	for ( var prop in source) {
		if (source.hasOwnProperty(prop)) {
			newClone[prop] = source[prop];
		}
	}
	return newClone;
}

function infer(engine, info, callback) {
	if (isEmpty(engine.agenda)) {
		callback(null, info);
		return;
	} else if (info.step > 0) {
		engine.emit("step", info);
		if (info.stop) {
			return;
		}
	}

	var current = engine.agenda;
	engine.agenda = {};

	var currentRules = [];
	for ( var ruleName in current) {
		if (current.hasOwnProperty(ruleName)) {
			currentRules.push({
				name : ruleName,
				exec : current[ruleName]
			});
		}
	}

	info.step++;

    engine.trace(function() { return "********** STEP " + info.step + " **********"; });

	process.nextTick(function() {
		execRules(engine, currentRules, info, callback);
	});

}

function execRules(engine, currentRules, info, callback) {  // jshint ignore:line

	if (currentRules.length === 0) {
		infer(engine, info, callback);
		return;
	}

	var rule = currentRules.pop();

	process.nextTick(function() {
        engine.trace(function() { return "(ER) Executing rule '" + rule.name + "'"; });
		var data = rule.exec(engine, function(data) {
			if (data) {
                var result = {
					rule : rule.name,
					step : info.step,
					data : data
                };
				info.results.push(result);
			}
			execRules(engine, currentRules, info, callback);
		});
	});
}

function isObject(value) {
	if ((value === null) || (value === undefined)) {
		return false;
	}
	if ((value instanceof Date) || (value instanceof Array)) {
		return false;
	}
	return (value instanceof Object);
}

function updateFacts(facts, namespace, state, engine) {
	var separator = ".";
	if (namespace === "") {
		separator = "";
	}
	for ( var key in state) {
		if (state.hasOwnProperty(key)) {
			var factName = namespace + separator + key;
			if (!isObject(state[key])) {
				if (!(state[key] instanceof Function)) {
					if (engine !== undefined) {
						engine.set(factName, state[key]);
					} else {
						facts[factName] = state[key];
					}
				}
			} else {
				updateFacts(facts, factName, state[key], engine);
			}
		}
	}
}

function assign(obj, keyStack, value) {
	var key = keyStack.pop();
	if (keyStack.length === 0) {
		obj[key] = value;
	} else {
		if (obj[key] === undefined) {
			obj[key] = {};
		}
		assign(obj[key], keyStack, value);
	}
}

function createFacts(state, facts) {
	for ( var factName in facts) {
		if (facts.hasOwnProperty(factName)) {
			var keys = factName.split(".").reverse();
			assign(state, keys, facts[factName]);
		}
	}
}

function addCompil(engine, compil, namespace) {
	var ruleName;
	var newRuleName;
	var factName;
    var newFactNAme

	var separator = ".";
	if (namespace === "") {
		separator = "";
	}

	for (ruleName in compil.rules) {
		if (compil.rules.hasOwnProperty(ruleName)) {
			newRuleName = namespace + separator + ruleName;
			engine.rules[newRuleName] = compil.rules[ruleName];
		}
	}

	for (var i = 0; i < compil.initRules.length; i++) {
		ruleName = compil.initRules[i];
		newRuleName = namespace + separator + ruleName;
		engine.agenda[newRuleName] = engine.rules[ruleName];
	}

	for (factName in compil.relations) {
		if (compil.relations.hasOwnProperty(factName)) {
			var relatedRules = compil.relations[factName];
            newFactName = namespace + separator + factName;
			engine.relations[newFactName] = {};
			for (ruleName in relatedRules) {
				if (relatedRules.hasOwnProperty(ruleName)) {
					newRuleName = namespace + separator + ruleName;
					engine.relations[newFactName][newRuleName] = true;
				}
			}
		}
	}

	for (factName in compil.facts) {
		if (compil.facts.hasOwnProperty(factName)) {
            newFactName = namespace + separator + factName;
			engine.set(newFactName, compil.facts[factName]);
		}
	}

}

/***** exports *****/

function Engine(compil) {

	this.facts = {};        // Map between fact names and fact values
	this.rules = {};        // Map between rule names and rules (function)
	this.relations = {};    // Map between fact names and all related rules
    this.wildRelations = {} // Map between wild facts and all related rules
	this.agenda = {};       // Rules to be executed by the next call to infer()

    // Note: A wild fact is a fact name that contains the wildcard '*'.
    // A wild fact can be general: "*.isHuman" or prefixed:
    // "container.*.weight". The wild fact prefix (if any) defines the rule
    // execution context. The rule execution context is composed by the
    // prefix and the actual wildcard value. For example, "container.*.weight"
    // will match both facts named "container.weight" and
    // "container.frame.weight". The first context is "container.",
    // the second context is "container.frame.". Context cannot be mixed in
    // the same rule.

    this.trace = function(message) {
        if (this.listeners("trace").length > 0) {
            if (typeof message === "function") {
                this.emit("trace", message());
            } else {
                this.emit("trace", message);
            }
        }
    };

	this.addRule = function(ruleName, rule) {
		var ruleContent = rule.toString();
		var regex = /self\.get\(["'](.*)["']\)/gm;
		var match = regex.exec(ruleContent);
		var factName;
        var prefix = "";
		this.rules[ruleName] = rule;
		while (match) {
			factName = match[1];
            if (factName.indexOf("*") === -1) {
                // we have a well defined rule
                if (this.relations[factName] === undefined) {
                    this.relations[factName] = {};
                }
                this.trace( function() {
                    return "(AE) Adding rule '" + ruleName +
                        "' to the engine relations.";
                });
                this.relations[factName][ruleName] = true;
            } else {
                // we have a wild rule
                var currentPrefix = factName.split("*")[0];
                if (prefix === "") {
                    prefix = currentPrefix;
                } else if (prefix != currentPrefix) {
                    throw "Context can not be changed for the same rule '" +
                        ruleName + "'. Initial context: '" + prefix +
                        "' new prefix: '" + currentPrefix +"'.";
                }
                var wildFactName = factName
                    .replace(".", "\\.")
                    .replace("*", "(.*)");
                if (this.wildRelations[wildFactName] === undefined) {
                    this.wildRelations[wildFactName] = {};
                }
                this.trace( function() {
                    return "(AE) Adding rule '" + ruleName +
                        "' to the engine wild relations.";
                });
                this.wildRelations[wildFactName][ruleName] = true;
            }
            match = regex.exec(ruleContent);
		}
	};

	/**
	 * Adds a set of rules to the engine.
	 */
	this.addRules = function(ruleSet, namespace) {
		var prefix = "";
		if (namespace) {
			prefix = namespace + ".";
		}
		for ( var ruleName in ruleSet) {
			if (ruleSet.hasOwnProperty(ruleName)) {
				this.addRule(prefix + ruleName, ruleSet[ruleName]);
			}
		}
	};

	this.addCompil = function(compil, namespace) {
		if (namespace === undefined) {
			namespace = "";
		}
		addCompil(this, compil, namespace);
	};

	this.get = function(factName) {
		var value = this.facts[factName];
		if (value instanceof Array) {
			// array facts are intransitive!
			var array = [];
			for (var i = 0; i < value.length; i++) {
				array.push(value[i]);
			}
			return array;
		} else {
			return value;
		}
	};

	this.set = function(factName, value) {
		if (this.facts[factName] !== value) {
            var self = this;
            this.trace(function() {
                return "(FC) Fact '" + factName + "' changed from '" +
                    self.facts[factName] + "' to '" + value + "'";
            });
			this.facts[factName] = value;
			if (this.relations[factName] !== undefined) {
				var rules = this.relations[factName];
                var plannedRules = []; // for tracing purpose only
				for ( var ruleName in rules) {
					if (rules.hasOwnProperty(ruleName)) {
                        if (typeof this.agenda[ruleName] !== "function") {
                            this.trace(function() {
                                return "(AA) Adding rule '" + ruleName +
                                    "' to the agenda.";
                            });
                            this.agenda[ruleName] = this.rules[ruleName];
                        }
					}
                }
			}
		}
	};

	this.infer = function(callback) {
        this.trace("(SI) Starting inference.");
		var engine = this;
		process.nextTick(function() {
			infer(engine, {
				step : 0,
				results : [],
				stop : false
			}, callback);
		});
	};

	this.getFacts = function() {
		var obj = {};
		createFacts(obj, this.facts);
		return obj;
	};

	this.setFacts = function(obj) {
		updateFacts(null, "", obj, this);
	};

	this.reset = function(newFacts) {
		this.facts = {};
		this.agenda = {};
		if (newFacts) {
			this.setFacts(newFacts);
		}
	};

	// initialize the engine with the compilation unit if provided
	if (compil === undefined) {
		// default empty compilation unit.
		compil = {
			facts : {},
			rules : {},
			relations : {},
			initRules : []
		};
	}

	addCompil(this, compil, "");
}

util.inherits(Engine, events.EventEmitter);

module.exports = Engine;

<commit_msg>modified rule regex to take into account rule names as variables<commit_after>"use strict";

var events = require("events");
var util = require("util");

/***** Private *****/

function isEmpty(obj) {
	for ( var key in obj) {
		if (obj.hasOwnProperty(key)) {
			return false;
		}
	}
	return true;
}

function clone(source) {
	var newClone = {};
	for ( var prop in source) {
		if (source.hasOwnProperty(prop)) {
			newClone[prop] = source[prop];
		}
	}
	return newClone;
}

function infer(engine, info, callback) {
	if (isEmpty(engine.agenda)) {
		callback(null, info);
		return;
	} else if (info.step > 0) {
		engine.emit("step", info);
		if (info.stop) {
			return;
		}
	}

	var current = engine.agenda;
	engine.agenda = {};

	var currentRules = [];
	for ( var ruleName in current) {
		if (current.hasOwnProperty(ruleName)) {
			currentRules.push({
				name : ruleName,
				exec : current[ruleName]
			});
		}
	}

	info.step++;

    engine.trace(function() { return "********** STEP " + info.step + " **********"; });

	process.nextTick(function() {
		execRules(engine, currentRules, info, callback);
	});

}

function execRules(engine, currentRules, info, callback) {  // jshint ignore:line

	if (currentRules.length === 0) {
		infer(engine, info, callback);
		return;
	}

	var rule = currentRules.pop();

	process.nextTick(function() {
        engine.trace(function() { return "(ER) Executing rule '" + rule.name + "'"; });
		var data = rule.exec(engine, function(data) {
			if (data) {
                var result = {
					rule : rule.name,
					step : info.step,
					data : data
                };
				info.results.push(result);
			}
			execRules(engine, currentRules, info, callback);
		});
	});
}

function isObject(value) {
	if ((value === null) || (value === undefined)) {
		return false;
	}
	if ((value instanceof Date) || (value instanceof Array)) {
		return false;
	}
	return (value instanceof Object);
}

function updateFacts(facts, namespace, state, engine) {
	var separator = ".";
	if (namespace === "") {
		separator = "";
	}
	for ( var key in state) {
		if (state.hasOwnProperty(key)) {
			var factName = namespace + separator + key;
			if (!isObject(state[key])) {
				if (!(state[key] instanceof Function)) {
					if (engine !== undefined) {
						engine.set(factName, state[key]);
					} else {
						facts[factName] = state[key];
					}
				}
			} else {
				updateFacts(facts, factName, state[key], engine);
			}
		}
	}
}

function assign(obj, keyStack, value) {
	var key = keyStack.pop();
	if (keyStack.length === 0) {
		obj[key] = value;
	} else {
		if (obj[key] === undefined) {
			obj[key] = {};
		}
		assign(obj[key], keyStack, value);
	}
}

function createFacts(state, facts) {
	for ( var factName in facts) {
		if (facts.hasOwnProperty(factName)) {
			var keys = factName.split(".").reverse();
			assign(state, keys, facts[factName]);
		}
	}
}

function addCompil(engine, compil, namespace) {
	var ruleName;
	var newRuleName;
	var factName;
    var newFactNAme

	var separator = ".";
	if (namespace === "") {
		separator = "";
	}

	for (ruleName in compil.rules) {
		if (compil.rules.hasOwnProperty(ruleName)) {
			newRuleName = namespace + separator + ruleName;
			engine.rules[newRuleName] = compil.rules[ruleName];
		}
	}

	for (var i = 0; i < compil.initRules.length; i++) {
		ruleName = compil.initRules[i];
		newRuleName = namespace + separator + ruleName;
		engine.agenda[newRuleName] = engine.rules[ruleName];
	}

	for (factName in compil.relations) {
		if (compil.relations.hasOwnProperty(factName)) {
			var relatedRules = compil.relations[factName];
            newFactName = namespace + separator + factName;
			engine.relations[newFactName] = {};
			for (ruleName in relatedRules) {
				if (relatedRules.hasOwnProperty(ruleName)) {
					newRuleName = namespace + separator + ruleName;
					engine.relations[newFactName][newRuleName] = true;
				}
			}
		}
	}

	for (factName in compil.facts) {
		if (compil.facts.hasOwnProperty(factName)) {
            newFactName = namespace + separator + factName;
			engine.set(newFactName, compil.facts[factName]);
		}
	}

}

/***** exports *****/

function Engine(compil) {

	this.facts = {};        // Map between fact names and fact values
	this.rules = {};        // Map between rule names and rules (function)
	this.relations = {};    // Map between fact names and all related rules
    this.wildRelations = {} // Map between wild facts and all related rules
	this.agenda = {};       // Rules to be executed by the next call to infer()

    // Note: A wild fact is a fact name that contains the wildcard '*'.
    // A wild fact can be general: "*.isHuman" or prefixed:
    // "container.*.weight". The wild fact prefix (if any) defines the rule
    // execution context. The rule execution context is composed by the
    // prefix and the actual wildcard value. For example, "container.*.weight"
    // will match both facts named "container.weight" and
    // "container.frame.weight". The first context is "container.",
    // the second context is "container.frame.". Context cannot be mixed in
    // the same rule.

    this.trace = function(message) {
        if (this.listeners("trace").length > 0) {
            if (typeof message === "function") {
                this.emit("trace", message());
            } else {
                this.emit("trace", message);
            }
        }
    };

	this.addRule = function(ruleName, rule) {
		var ruleContent = rule.toString();
		var regex = /self\.get\(["']?(.*?)["']?\)/gm;
		var match = regex.exec(ruleContent);
		var factName;
        var prefix = "";
		this.rules[ruleName] = rule;
		while (match) {
			factName = match[1];
            if (factName.indexOf("*") === -1) {
                // we have a well defined rule
                if (this.relations[factName] === undefined) {
                    this.relations[factName] = {};
                }
                this.trace( function() {
                    return "(AE) Adding rule '" + ruleName +
                        "' to the engine relations.";
                });
                this.relations[factName][ruleName] = true;
            } else {
                // we have a wild rule
                var currentPrefix = factName.split("*")[0];
                if (prefix === "") {
                    prefix = currentPrefix;
                } else if (prefix != currentPrefix) {
                    throw "Context can not be changed for the same rule '" +
                        ruleName + "'. Initial context: '" + prefix +
                        "' new prefix: '" + currentPrefix +"'.";
                }
                var wildFactName = factName
                    .replace(".", "\\.")
                    .replace("*", "(.*)");
                if (this.wildRelations[wildFactName] === undefined) {
                    this.wildRelations[wildFactName] = {};
                }
                this.trace( function() {
                    return "(AE) Adding rule '" + ruleName +
                        "' to the engine *wild* relations.";
                });
                this.wildRelations[wildFactName][ruleName] = true;
            }
            match = regex.exec(ruleContent);
		}
	};

	/**
	 * Adds a set of rules to the engine.
	 */
	this.addRules = function(ruleSet, namespace) {
		var prefix = "";
		if (namespace) {
			prefix = namespace + ".";
		}
		for ( var ruleName in ruleSet) {
			if (ruleSet.hasOwnProperty(ruleName)) {
				this.addRule(prefix + ruleName, ruleSet[ruleName]);
			}
		}
	};

	this.addCompil = function(compil, namespace) {
		if (namespace === undefined) {
			namespace = "";
		}
		addCompil(this, compil, namespace);
	};

	this.get = function(factName) {
		var value = this.facts[factName];
		if (value instanceof Array) {
			// array facts are intransitive!
			var array = [];
			for (var i = 0; i < value.length; i++) {
				array.push(value[i]);
			}
			return array;
		} else {
			return value;
		}
	};

	this.set = function(factName, value) {
		if (this.facts[factName] !== value) {
            var self = this;
            this.trace(function() {
                return "(FC) Fact '" + factName + "' changed from '" +
                    self.facts[factName] + "' to '" + value + "'";
            });
			this.facts[factName] = value;
			if (this.relations[factName] !== undefined) {
				var rules = this.relations[factName];
                var plannedRules = []; // for tracing purpose only
				for ( var ruleName in rules) {
					if (rules.hasOwnProperty(ruleName)) {
                        if (typeof this.agenda[ruleName] !== "function") {
                            this.trace(function() {
                                return "(AA) Adding rule '" + ruleName +
                                    "' to the agenda.";
                            });
                            this.agenda[ruleName] = this.rules[ruleName];
                        }
					}
                }
			}
		}
	};

	this.infer = function(callback) {
        this.trace("(SI) Starting inference.");
		var engine = this;
		process.nextTick(function() {
			infer(engine, {
				step : 0,
				results : [],
				stop : false
			}, callback);
		});
	};

	this.getFacts = function() {
		var obj = {};
		createFacts(obj, this.facts);
		return obj;
	};

	this.setFacts = function(obj) {
		updateFacts(null, "", obj, this);
	};

	this.reset = function(newFacts) {
		this.facts = {};
		this.agenda = {};
		if (newFacts) {
			this.setFacts(newFacts);
		}
	};

	// initialize the engine with the compilation unit if provided
	if (compil === undefined) {
		// default empty compilation unit.
		compil = {
			facts : {},
			rules : {},
			relations : {},
			initRules : []
		};
	}

	addCompil(this, compil, "");
}

util.inherits(Engine, events.EventEmitter);

module.exports = Engine;

<|endoftext|>


<commit_before>import './StickyBottom.scss';
import {
  qs,
  rAF,
  getScrollPosition,
  getViewHeight,
  addCss,
  delCss,
} from './helpers';

const BCR = 'getBoundingClientRect';
const RENDERING_MODES = {
  TWO_STATES: 'two-states',
  THREE_STATES: 'three-states',
};

export default class StickyBottom {
  constructor(props) {
    const defaultProps = {
      elems: {
        area: '.js.sticky-bottom',
        box: '.sticky-bottom__box',
        boundary: '.sticky-bottom__boundary',
      },
    };
    this.props = { ...defaultProps, ...props };

    this.state = {
      scroll: {},
      rect: {},
    };

    this.onScroll = this.onScroll.bind(this);
    this.onResize = this.onResize.bind(this);
  }

  init() {
    const areaEl = qs(this.props.elems.area);
    this.elems = {
      area: areaEl,
      box: qs(this.props.elems.box, areaEl),
      boundary: qs(this.props.elems.boundary, areaEl),
    };
    if (this.props.debug) {
      this.elems.debug = qs(this.props.debug);
    }
    if (this.props.renderingMode === RENDERING_MODES.TWO_STATES) {
      this.elems.area.classList.add('sticky-bottom--rendering-mode-two-states');
    }

    this.initAndUpdateDimensions();

    this.updateDOM({ forceUpdate: true });
    window.addEventListener('scroll', this.onScroll);
    window.addEventListener('resize', this.onResize);
    return this;
  }
  initAndUpdateDimensions() {
    this.state.rect = {
      ...this.state.rect,
      area: this.elems.area[BCR](),
      box: this.elems.box[BCR](),
      boundary: this.elems.boundary[BCR](),
    };
    this.state.scroll = {
      ...this.state.scroll,
      viewHeight: getViewHeight(),
      scrollStartPosition: getScrollPosition(),
      scrollPosition: getScrollPosition(),
    };

    this.updateDimensions();
    return this;
  }

  updateDimensions() {
    let stickyMode = '';
    const { stickyMode: stickyModePrev } = this.state.scroll;

    const { area } = this.state.rect;
    const {
      viewHeight,
      scrollStartPosition,
      scrollPosition,
    } = this.state.scroll;

    const areaTop = area.top + scrollStartPosition;
    const areaBottom = area.bottom + scrollStartPosition;

    const areaIsAfterScroll = scrollPosition - areaTop;

    const viewIsBeforeArea = areaBottom - scrollPosition - viewHeight;

    if (area.height - viewHeight <= 0) {
      stickyMode = 'after';
    } else if (areaIsAfterScroll <= 0 && viewIsBeforeArea >= 0) {
      stickyMode = 'before';
    } else if (areaIsAfterScroll >= 0 && viewIsBeforeArea >= 0) {
      stickyMode = 'fixed';
    } else {
      stickyMode = 'after';
    }

    this.state.scroll = {
      ...this.state.scroll,
      scrollPosition,
      stickyMode,
      stickyModePrev,
    };

    return this;
  }

  updateDOM({ forceUpdate } = {}) {
    const { area } = this.state.rect;
    const {
      viewHeight,
      stickyMode,
      stickyModePrev,
    } = this.state.scroll;

    const renderAsBefore = () => {
      if (this.elems.debug) {
        this.elems.debug.textContent = 'debug: state before';
      }
      const el = this.elems.box;
      el.style.position = 'absolute';
      el.style.bottom = `${area.height - viewHeight}px`;
      el.style.left = '';
      el.style.width = '';
      delCss(this.elems.area, ['sticky-bottom--is-fixed', 'sticky-bottom--is-after']);
      addCss(this.elems.area, 'sticky-bottom--is-before');
    };

    const renderAsFixed = () => {
      const el = this.elems.box;
      el.style.position = 'fixed';
      el.style.bottom = '';
      el.style.left = `${area.left}px`;
      el.style.width = `${area.width}px`;
      delCss(this.elems.area, ['sticky-bottom--is-before', 'sticky-bottom--is-after']);
      addCss(this.elems.area, 'sticky-bottom--is-fixed');
    };

    const renderAsAfter = () => {
      if (this.elems.debug) {
        this.elems.debug.textContent = 'debug: state after';
      }
      const el = this.elems.box;
      el.style.position = 'absolute';
      el.style.bottom = '';
      el.style.left = '';
      el.style.width = '';
      delCss(this.elems.area, ['sticky-bottom--is-before', 'sticky-bottom--is-fixed']);
      addCss(this.elems.area, 'sticky-bottom--is-after');
    };

    if (stickyMode === 'before') {
      if (!forceUpdate && stickyModePrev === stickyMode) {
        // no DOM update needed
        return this;
      }

      if (this.props.renderingMode === 'two-states') {
        renderAsAfter();
        return this;
      }

      renderAsBefore();
    } else if (stickyMode === 'fixed') {
      if (!forceUpdate && stickyModePrev === stickyMode) {
        // no DOM update needed
        return this;
      }
      renderAsFixed();
    } else if (stickyMode === 'after') {
      if (!forceUpdate && stickyModePrev === stickyMode) {
        // no DOM update needed
        return this;
      }
      renderAsAfter();
    }

    return this;
  }

  destroy() {
    window.removeEventListener('scroll', this.onScroll);
    window.removeEventListener('resize', this.onResize);
    return this;
  }

  onScroll() {
    // https://developer.mozilla.org/en-US/docs/Web/Events/scroll
    this.state.scroll.scrollPosition = getScrollPosition();
    if (!this.state.scroll.ticking) {
      rAF(() => {
        this.updateDimensions();
        this.updateDOM();
        this.state.scroll.ticking = false;
      });
    }
    this.state.scroll.ticking = true;
  }

  onResize() {
    rAF(() => {
      this.initAndUpdateDimensions();
      this.updateDOM({ forceUpdate: true });
    });
  }
}
<commit_msg>fix debug info missing for fixed<commit_after>import './StickyBottom.scss';
import {
  qs,
  rAF,
  getScrollPosition,
  getViewHeight,
  addCss,
  delCss,
} from './helpers';

const BCR = 'getBoundingClientRect';
const RENDERING_MODES = {
  TWO_STATES: 'two-states',
  THREE_STATES: 'three-states',
};

export default class StickyBottom {
  constructor(props) {
    const defaultProps = {
      elems: {
        area: '.js.sticky-bottom',
        box: '.sticky-bottom__box',
        boundary: '.sticky-bottom__boundary',
      },
    };
    this.props = { ...defaultProps, ...props };

    this.state = {
      scroll: {},
      rect: {},
    };

    this.onScroll = this.onScroll.bind(this);
    this.onResize = this.onResize.bind(this);
  }

  init() {
    const areaEl = qs(this.props.elems.area);
    this.elems = {
      area: areaEl,
      box: qs(this.props.elems.box, areaEl),
      boundary: qs(this.props.elems.boundary, areaEl),
    };
    if (this.props.debug) {
      this.elems.debug = qs(this.props.debug);
    }
    if (this.props.renderingMode === RENDERING_MODES.TWO_STATES) {
      this.elems.area.classList.add('sticky-bottom--rendering-mode-two-states');
    }

    this.initAndUpdateDimensions();

    this.updateDOM({ forceUpdate: true });
    window.addEventListener('scroll', this.onScroll);
    window.addEventListener('resize', this.onResize);
    return this;
  }
  initAndUpdateDimensions() {
    this.state.rect = {
      ...this.state.rect,
      area: this.elems.area[BCR](),
      box: this.elems.box[BCR](),
      boundary: this.elems.boundary[BCR](),
    };
    this.state.scroll = {
      ...this.state.scroll,
      viewHeight: getViewHeight(),
      scrollStartPosition: getScrollPosition(),
      scrollPosition: getScrollPosition(),
    };

    this.updateDimensions();
    return this;
  }

  updateDimensions() {
    let stickyMode = '';
    const { stickyMode: stickyModePrev } = this.state.scroll;

    const { area } = this.state.rect;
    const {
      viewHeight,
      scrollStartPosition,
      scrollPosition,
    } = this.state.scroll;

    const areaTop = area.top + scrollStartPosition;
    const areaBottom = area.bottom + scrollStartPosition;

    const areaIsAfterScroll = scrollPosition - areaTop;

    const viewIsBeforeArea = areaBottom - scrollPosition - viewHeight;

    if (area.height - viewHeight <= 0) {
      stickyMode = 'after';
    } else if (areaIsAfterScroll <= 0 && viewIsBeforeArea >= 0) {
      stickyMode = 'before';
    } else if (areaIsAfterScroll >= 0 && viewIsBeforeArea >= 0) {
      stickyMode = 'fixed';
    } else {
      stickyMode = 'after';
    }

    this.state.scroll = {
      ...this.state.scroll,
      scrollPosition,
      stickyMode,
      stickyModePrev,
    };

    return this;
  }

  updateDOM({ forceUpdate } = {}) {
    const { area } = this.state.rect;
    const {
      viewHeight,
      stickyMode,
      stickyModePrev,
    } = this.state.scroll;

    const renderAsBefore = () => {
      if (this.elems.debug) {
        this.elems.debug.textContent = 'debug: state before';
      }
      const el = this.elems.box;
      el.style.position = 'absolute';
      el.style.bottom = `${area.height - viewHeight}px`;
      el.style.left = '';
      el.style.width = '';
      delCss(this.elems.area, ['sticky-bottom--is-fixed', 'sticky-bottom--is-after']);
      addCss(this.elems.area, 'sticky-bottom--is-before');
    };

    const renderAsFixed = () => {
      if (this.elems.debug) {
        this.elems.debug.textContent = 'debug: state fixed';
      }
      const el = this.elems.box;
      el.style.position = 'fixed';
      el.style.bottom = '';
      el.style.left = `${area.left}px`;
      el.style.width = `${area.width}px`;
      delCss(this.elems.area, ['sticky-bottom--is-before', 'sticky-bottom--is-after']);
      addCss(this.elems.area, 'sticky-bottom--is-fixed');
    };

    const renderAsAfter = () => {
      if (this.elems.debug) {
        this.elems.debug.textContent = 'debug: state after';
      }
      const el = this.elems.box;
      el.style.position = 'absolute';
      el.style.bottom = '';
      el.style.left = '';
      el.style.width = '';
      delCss(this.elems.area, ['sticky-bottom--is-before', 'sticky-bottom--is-fixed']);
      addCss(this.elems.area, 'sticky-bottom--is-after');
    };

    if (stickyMode === 'before') {
      if (!forceUpdate && stickyModePrev === stickyMode) {
        // no DOM update needed
        return this;
      }

      if (this.props.renderingMode === 'two-states') {
        renderAsAfter();
        return this;
      }

      renderAsBefore();
    } else if (stickyMode === 'fixed') {
      if (!forceUpdate && stickyModePrev === stickyMode) {
        // no DOM update needed
        return this;
      }
      renderAsFixed();
    } else if (stickyMode === 'after') {
      if (!forceUpdate && stickyModePrev === stickyMode) {
        // no DOM update needed
        return this;
      }
      renderAsAfter();
    }

    return this;
  }

  destroy() {
    window.removeEventListener('scroll', this.onScroll);
    window.removeEventListener('resize', this.onResize);
    return this;
  }

  onScroll() {
    // https://developer.mozilla.org/en-US/docs/Web/Events/scroll
    this.state.scroll.scrollPosition = getScrollPosition();
    if (!this.state.scroll.ticking) {
      rAF(() => {
        this.updateDimensions();
        this.updateDOM();
        this.state.scroll.ticking = false;
      });
    }
    this.state.scroll.ticking = true;
  }

  onResize() {
    rAF(() => {
      this.initAndUpdateDimensions();
      this.updateDOM({ forceUpdate: true });
    });
  }
}
<|endoftext|>


<commit_before>test(
  'InsertionTest',

  [
    'ephox.compass.Arr',
    'ephox.peanut.Fun',
    'ephox.scullion.Struct',
    'ephox.snooker.api.Structs',
    'ephox.snooker.model.Warehouse',
    'ephox.snooker.operate.ColumnModification'
  ],

  function (Arr, Fun, Struct, Structs, Warehouse, ColumnModification) {
    var check = function (expected, method, input, rowIndex, colIndex) {
      var warehouse = Warehouse.generate(input);
      var actual = method(warehouse, rowIndex, colIndex, {
        cell: function () {
          return d('?', 1, 1);
        },
        replace: function (cell, tag, attrs) {
          return tag + '_' + cell + '>>' + attrs.scope;
        }
      });

      assert.eq(expected.length, actual.length);
      Arr.each(expected, function (exp, i) {
        var act = actual[i];
        var raw = Arr.map(act.cells(), function (cell) {
          return {
            element: cell.element(),
            colspan: cell.colspan(),
            rowspan: cell.rowspan()
          };
        });

        assert.eq(exp, raw);
      });
    };

    var r = Struct.immutable('element', 'cells');
    var d = Structs.detail;

    var generate = function () {
      return [
        r('r0', [ d('a', 1, 1), d('b', 1, 1) ]),
        r('r1', [ d('c', 1, 2) ])
      ];
    };

    check([
      [ { element: 'b', colspan: 1, rowspan: 1 } ],
      [ { element: 'c', colspan: 1, rowspan: 1 } ]
    ], ColumnModification.erase, generate(), 0, 0);

    check([
      [ { element: 'a', colspan: 1, rowspan: 1 } ],
      [ { element: 'c', colspan: 1, rowspan: 1 } ]
    ], ColumnModification.erase, generate(), 0, 1);

    check([
      [ { element: 'a', colspan: 1, rowspan: 1 }, { element: '?', colspan: 1, rowspan: 1 } ]
    ], ColumnModification.insertAfter, [ r('r0', [ d('a', 1, 1) ]) ], 0, 0);

    check([
      [ { element: '?', colspan: 1, rowspan: 1 }, { element: 'a', colspan: 1, rowspan: 1 } ]
    ], ColumnModification.insertBefore, [ r('r0', [ d('a', 1, 1) ]) ], 0, 0);

    check([
      [ { element: 'a', colspan: 1, rowspan: 1 }, { element: '?', colspan: 1, rowspan: 1 }, { element: 'b', colspan: 1, rowspan: 1 } ],
      [ { element: 'c', colspan: 3, rowspan: 1 } ]
    ], ColumnModification.insertAfter, generate(), 0, 0);

    check([
      [ { element: 'a', colspan: 1, rowspan: 1 }, { element: 'b', colspan: 1, rowspan: 1 }, { element: '?', colspan: 1, rowspan: 1 } ],
      [ { element: 'c', colspan: 2, rowspan: 1 }, { element: '?', colspan: 1, rowspan: 1 } ]
    ], ColumnModification.insertAfter, generate(), 0, 1);

    check([
      [ { element: '?', colspan: 1, rowspan: 1 }, { element: 'a', colspan: 1, rowspan: 1 }, { element: 'b', colspan: 1, rowspan: 1 } ],
      [ { element: '?', colspan: 1, rowspan: 1 }, { element: 'c', colspan: 2, rowspan: 1 } ]
    ], ColumnModification.insertBefore, generate(), 0, 0);

    check([
      [ { element: 'a', colspan: 1, rowspan: 1 }, { element: '?', colspan: 1, rowspan: 1 }, { element: 'b', colspan: 1, rowspan: 1 } ],
      [ { element: 'c', colspan: 3, rowspan: 1 } ]
    ], ColumnModification.insertBefore, generate(), 0, 1);

    check([
      [ { element: '?', colspan: 1, rowspan: 1 }, { element: 'a1', colspan: 1, rowspan: 1 }, { element: 'a2', colspan: 1, rowspan: 1 } ],
      [ { element: '?', colspan: 1, rowspan: 1 }, { element: 'b', colspan: 1, rowspan: 2 }, { element: 'c', colspan: 1, rowspan: 1 } ],
      [ { element: '?', colspan: 1, rowspan: 1 }, { element: 'd', colspan: 1, rowspan: 1 } ]
    ], ColumnModification.insertBefore, [
      r('r0', [ d('a1', 1, 1), d('a2', 1, 1) ]),
      r('r1', [ d('b', 2, 1), d('c', 1, 1) ]),
      r('r2', [ d('d', 1, 1) ])
    ], 0, 0);

    check([
      [ { element: 'a', colspan: 1, rowspan: 1 }, { element: 'th_b>>row', colspan: 1, rowspan: 1 } ],
      [ { element: 'th_c>>row', colspan: 2, rowspan: 1 } ]
    ], ColumnModification.makeHeader, generate(), 0, 1);

    check([
      [ { element: 'a', colspan: 1, rowspan: 1 }, { element: 'td_b>>null', colspan: 1, rowspan: 1 } ],
      [ { element: 'td_c>>null', colspan: 2, rowspan: 1 } ]
    ], ColumnModification.unmakeHeader, generate(), 0, 1);
  }
);
<commit_msg>TBIO-549: Test scope for unmakeHeader on column in similar way to row.<commit_after>test(
  'InsertionTest',

  [
    'ephox.compass.Arr',
    'ephox.peanut.Fun',
    'ephox.scullion.Struct',
    'ephox.snooker.api.Structs',
    'ephox.snooker.model.Warehouse',
    'ephox.snooker.operate.ColumnModification'
  ],

  function (Arr, Fun, Struct, Structs, Warehouse, ColumnModification) {
    var check = function (expected, method, input, rowIndex, colIndex) {
      var warehouse = Warehouse.generate(input);
      var actual = method(warehouse, rowIndex, colIndex, {
        cell: function () {
          return d('?', 1, 1);
        },
        replace: function (cell, tag, attrs) {
          var scope = attrs.scope === null? '' : '>>' + attrs.scope;
          return tag + '_' + cell + scope;
        }
      });

      assert.eq(expected.length, actual.length);
      Arr.each(expected, function (exp, i) {
        var act = actual[i];
        var raw = Arr.map(act.cells(), function (cell) {
          return {
            element: cell.element(),
            colspan: cell.colspan(),
            rowspan: cell.rowspan()
          };
        });

        assert.eq(exp, raw);
      });
    };

    var r = Struct.immutable('element', 'cells');
    var d = Structs.detail;

    var generate = function () {
      return [
        r('r0', [ d('a', 1, 1), d('b', 1, 1) ]),
        r('r1', [ d('c', 1, 2) ])
      ];
    };

    check([
      [ { element: 'b', colspan: 1, rowspan: 1 } ],
      [ { element: 'c', colspan: 1, rowspan: 1 } ]
    ], ColumnModification.erase, generate(), 0, 0);

    check([
      [ { element: 'a', colspan: 1, rowspan: 1 } ],
      [ { element: 'c', colspan: 1, rowspan: 1 } ]
    ], ColumnModification.erase, generate(), 0, 1);

    check([
      [ { element: 'a', colspan: 1, rowspan: 1 }, { element: '?', colspan: 1, rowspan: 1 } ]
    ], ColumnModification.insertAfter, [ r('r0', [ d('a', 1, 1) ]) ], 0, 0);

    check([
      [ { element: '?', colspan: 1, rowspan: 1 }, { element: 'a', colspan: 1, rowspan: 1 } ]
    ], ColumnModification.insertBefore, [ r('r0', [ d('a', 1, 1) ]) ], 0, 0);

    check([
      [ { element: 'a', colspan: 1, rowspan: 1 }, { element: '?', colspan: 1, rowspan: 1 }, { element: 'b', colspan: 1, rowspan: 1 } ],
      [ { element: 'c', colspan: 3, rowspan: 1 } ]
    ], ColumnModification.insertAfter, generate(), 0, 0);

    check([
      [ { element: 'a', colspan: 1, rowspan: 1 }, { element: 'b', colspan: 1, rowspan: 1 }, { element: '?', colspan: 1, rowspan: 1 } ],
      [ { element: 'c', colspan: 2, rowspan: 1 }, { element: '?', colspan: 1, rowspan: 1 } ]
    ], ColumnModification.insertAfter, generate(), 0, 1);

    check([
      [ { element: '?', colspan: 1, rowspan: 1 }, { element: 'a', colspan: 1, rowspan: 1 }, { element: 'b', colspan: 1, rowspan: 1 } ],
      [ { element: '?', colspan: 1, rowspan: 1 }, { element: 'c', colspan: 2, rowspan: 1 } ]
    ], ColumnModification.insertBefore, generate(), 0, 0);

    check([
      [ { element: 'a', colspan: 1, rowspan: 1 }, { element: '?', colspan: 1, rowspan: 1 }, { element: 'b', colspan: 1, rowspan: 1 } ],
      [ { element: 'c', colspan: 3, rowspan: 1 } ]
    ], ColumnModification.insertBefore, generate(), 0, 1);

    check([
      [ { element: '?', colspan: 1, rowspan: 1 }, { element: 'a1', colspan: 1, rowspan: 1 }, { element: 'a2', colspan: 1, rowspan: 1 } ],
      [ { element: '?', colspan: 1, rowspan: 1 }, { element: 'b', colspan: 1, rowspan: 2 }, { element: 'c', colspan: 1, rowspan: 1 } ],
      [ { element: '?', colspan: 1, rowspan: 1 }, { element: 'd', colspan: 1, rowspan: 1 } ]
    ], ColumnModification.insertBefore, [
      r('r0', [ d('a1', 1, 1), d('a2', 1, 1) ]),
      r('r1', [ d('b', 2, 1), d('c', 1, 1) ]),
      r('r2', [ d('d', 1, 1) ])
    ], 0, 0);

    check([
      [ { element: 'a', colspan: 1, rowspan: 1 }, { element: 'th_b>>row', colspan: 1, rowspan: 1 } ],
      [ { element: 'th_c>>row', colspan: 2, rowspan: 1 } ]
    ], ColumnModification.makeHeader, generate(), 0, 1);

    check([
      [ { element: 'a', colspan: 1, rowspan: 1 }, { element: 'td_b', colspan: 1, rowspan: 1 } ],
      [ { element: 'td_c', colspan: 2, rowspan: 1 } ]
    ], ColumnModification.unmakeHeader, generate(), 0, 1);
  }
);
<|endoftext|>