<commit_before>/**
 * Created by nferon on 06/06/16.
 */
angular.module('sbAdminApp')
.directive('sigmaJs', function() {
    return {
        restrict: 'E',
        template: '

',
        scope: {
            graph: '=',
            width: '@',
            height: '@',
            id: '@',
            threshold: '@?',
            edgeLabels: '=?',
            locate: '=?',
            timeFilter: '=?',
            eventCatcher: '&',
            metricFilter: '@?',
            metricMinFilter: '@?',
            metricMaxFilter: '@?',
            metricNEutralFilter: '@?',
            interactor: '@?',
            cleanRefresh: '@?',
            s: '=?',
            lasso: '=?',
            maxNodeSize: '=?'
        },
        link: function (scope, element) {
            // default values
            if (scope.threshold == undefined)
                scope.threshold = 4;
            if (scope.edgeLabels == undefined)
                scope.edgeLabels = false;
            if (scope.metricMinFilter == undefined)
                scope.metricMinFilter = 1;
            if (scope.metricMaxFilter == undefined)
                scope.metricMaxFilter = 10;
            if (scope.metricNeutralFilter == undefined)
                scope.metricNeutralFilter = -1;
            var neighbourhood = {}
            neighbourhood.adjacentNodes = [];
            neighbourhood.adjacentEdges = [];
            neighbourhood.nodesColour = [];
            neighbourhood.edgesColour = [];

            // Create sigma instance
            var s = new sigma({
                renderer: {
                    container: element[0].firstChild,
                    type: 'canvas'
                },
                settings: {
                    labelThreshold: scope.threshold,
                    labelSize: "fixed",
                    drawEdgeLabels: scope.edgeLabels,
                    minArrowSize: 5,
                    enableEdgeHovering: false,
                    edgeHoverColor: '#000',
                    defaultEdgeHoverColor: '#000',
                    edgeHoverSizeRatio: 2,
                    edgeHoverExtremities: true,
                    maxNodeSize: scope.maxNodeSize
                }
            });
            scope.s = s;
            /**** Plugins ****/

            /**** Interactions ****/
            function arrayUnique(array) {
              var a = array.concat();
              for(var i=0; i<a.length; ++i) {
                for(var j=i+1; j<a.length; ++j) {
                  if(a[i] === a[j])
                    a.splice(j--, 1);
                }
              }
              return a;
            };
            function resetHighlight() {
              for(var tmp=0; tmp<neighbourhood.adjacentNodes.length ; tmp++) {
                neighbourhood.adjacentNodes[tmp].size/=2;
                neighbourhood.adjacentNodes[tmp].color = neighbourhood.nodesColour[tmp];
              }
              for(var tmp=0; tmp<neighbourhood.adjacentEdges.length ; tmp++) {
                neighbourhood.adjacentEdges[tmp].size/=2;
                neighbourhood.adjacentEdges[tmp].color=neighbourhood.edgesColour[tmp];
              }
              neighbourhood.adjacentNodes = [];
              neighbourhood.adjacentEdges = [];
            }
            function highlightNeighbourhood(e) {
              var node = e.data.node;
              resetHighlight();

              // Get adjacent nodes:
              neighbourhood.adjacentNodes = neighbourhood.adjacentNodes.concat(s.graph.adjacentNodes(node.id));

              // Add hovered nodes to the array and remove duplicates:
              neighbourhood.adjacentNodes = arrayUnique(neighbourhood.adjacentNodes.concat(node));

              // Get adjacent edges:
              neighbourhood.adjacentEdges = neighbourhood.adjacentEdges.concat(s.graph.adjacentEdges(node.id));

              // Remove duplicates:
              neighbourhood.adjacentEdges = arrayUnique(neighbourhood.adjacentEdges);
              for(var tmp=0; tmp<neighbourhood.adjacentNodes.length ; tmp++) {
                neighbourhood.adjacentNodes[tmp].size*=2;
                neighbourhood.nodesColour[tmp] = neighbourhood.adjacentNodes[tmp].color;
                neighbourhood.adjacentNodes[tmp].color="rgb(0,0,200)"
              }
              for(var tmp=0; tmp<neighbourhood.adjacentEdges.length ; tmp++) {
                neighbourhood.adjacentEdges[tmp].size*=2;
                neighbourhood.edgesColour[tmp] = neighbourhood.adjacentEdges[tmp].color;
                neighbourhood.adjacentEdges[tmp].color="rgb(0,0,200)"
              }
              s.refresh();
            }

            /**** Bind Event ****/
            s.bind('clickEdges hovers', function(e) {
                scope.eventCatcher()(e);
            });

            s.bind('clickNode', function(e) {
                scope.eventCatcher()(e);
                if (scope.interactor == "neighbourhood") {
                    highlightNeighbourhood(e);
                }
            });

            /**** locate ****/
            if (scope.locate != undefined) {
                var conf = {
                    animation: {
                        node: {duration: 800},
                        edge: {duration: 800},
                        center: {duration: 300}
                    },
                    focusOut: false,
                    zoomDef: 1
                };
                var locate = sigma.plugins.locate(s, conf);

                locate.setPadding({
                    top: 10,
                    bottom: 10,
                    right: 10,
                    left: 10
                });
                scope.$watch('locate', function (newVal, oldVal) {
                    if(newVal.toString() != oldVal.toString()) {
                        var nodes = s.graph.nodes().filter(function (n) {
                            if (n.user_id != undefined && scope.locate.indexOf(parseInt(n.user_id)) != -1) {
                                //todo use color code
                                n.color = "rgb(51,122,183)";
                                return true;
                            }
                            else if (n.post_id != undefined && scope.locate.indexOf(parseInt(n.post_id)) != -1) {
                                n.color = "rgb(92,184,92)";
                                return true;
                            }
                            else if (n.comment_id != undefined && scope.locate.indexOf(parseInt(n.comment_id)) != -1) {
                                n.color = "rgb(240, 173, 78)";
                                return true;
                            }
                            else
                                n.color = "rgb(128, 128, 128)";
                        }).map(function (n) {
                            return n.id;
                        });
                        if (nodes.length > 0)
                            locate.nodes(nodes);
                        else
                            locate.center(conf.zoomDef);
                    }
                });
            }

            /**** Filter ****/
            scope.$watch('timeFilter', function () {
                s.graph.nodes().filter(function (n) {
                    if (n.user_id != undefined && scope.timeFilter.start <= n.timestamp && scope.timeFilter.end >= n.timestamp) {
                        //todo use color code
                        n.color = "rgb(51,122,183)";
                        return true;
                    }
                    else if (n.post_id != undefined && scope.timeFilter.start <= n.timestamp && scope.timeFilter.end >= n.timestamp) {
                        //todo use color code
                        n.color = "rgb(92,184,92)";
                        return true;
                    }
                    else if  (n.comment_id != undefined && scope.timeFilter.start <= n.timestamp && scope.timeFilter.end >= n.timestamp) {
                        n.color = "rgb(240, 173, 78)";
                        return true;
                    }
                    else
                        n.color = "rgb(128, 128, 128)";
                }).map(function (n) {
                    return n.id;
                });
                s.refresh();
            });

            var filter = new sigma.plugins.filter(s);
            var metricFilter = function(metricMin, metricMax) {
                filter
                  .undo('nodeFilter')
                  .nodesBy(function(n) {
                    return (((Number(n[scope.metricFilter]) >= Number(metricMin) && Number(n[scope.metricFilter]) <= Number(metricMax)) || Number(n[scope.metricFilter]) == Number(scope.metricNeutralFilter)));
                  }, 'nodeFilter')
                  .apply();
                filter
                  .undo('edgeFilter')
                  .edgesBy(function(e) {
                    return (Number(e[scope.metricFilter]) >= Number(metricMin) && Number(e[scope.metricFilter]) <= Number(metricMax));
                  }, 'edgeFilter')
                  .apply();
            }

            /**** Watch for update ****/
            scope.$watch('metricMinFilter', function(newVal) {
                metricFilter(newVal, scope.metricMaxFilter);
            });
            scope.$watch('metricMaxFilter', function(newVal) {
                metricFilter(scope.metricMinFilter, newVal);
            });
            scope.$watch('graph', function() {
                if (scope.graph != undefined && scope.graph != []){
                  s.graph.clear();
                  s.graph.read(scope.graph);
                  if (s.graph.nodes().length > 0){
                    s.ready = true;
                  }
                  s.refresh();
                }

            });
            scope.$watch('edgeLabels', function(newVal) {
                if (scope.graph != undefined && scope.graph != []){
                  s.graph.clear();
                  s.settings({
                      drawEdgeLabels: newVal
                  });
                  s.graph.read(scope.graph);
                  s.refresh();
                }
            });
            scope.$watch('threshold', function(newVal) {
                if (scope.graph != undefined && scope.graph != []){
                  //s.graph.clear();
                  s.settings({
                      labelThreshold: newVal
                  });
                  //s.graph.read(scope.graph);
                  s.refresh();
                }
            });
            scope.$watch('width', function() {
                element.children().css("width",scope.width);
                s.refresh();
                window.dispatchEvent(new Event('resize')); //hack so that it will be shown instantly
            });
            scope.$watch('height', function() {
                element.children().css("height",scope.height);
                s.refresh();
                window.dispatchEvent(new Event('resize'));//hack so that it will be shown instantly
            });
            scope.$watch('cleanRefresh', function() {
                resetHighlight();
                s.refresh();
            });

            var lasso = new sigma.plugins.lasso(s, s.renderers[0], {
              'strokeStyle': 'black',
              'lineWidth': 2,
              'fillWhileDrawing': true,
              'fillStyle': 'rgba(41, 41, 41, 0.2)',
              'cursor': 'crosshair'
            });
            scope.lasso = lasso;

            scope.$watch('interactor', function() {
                if (scope.interactor == 'dragNode') {
                    //console.log('change to drag')
                    // Instanciate the ActiveState plugin:
                    var activeState = sigma.plugins.activeState(s);

                    // Initialize the dragNodes plugin:
                    var dragListener = sigma.plugins.dragNodes(s, s.renderers[0], activeState);
                } else {
                    sigma.plugins.killDragNodes(s);
                }

                if (scope.interactor == 'lasso') {
                  lasso.activate();
                }
                else{
                  lasso.deactivate();
                }

                //console.log('change to lasso');
                if (scope.interactor == 'navigate') {
                    //console.log('change to navigate')
                }
                //var dragListener = new sigma.plugins.dragNodes(sigmaInstance, renderer, activeState);

            });

            element.on('$destroy', function() {
                s.graph.clear();
            });
        }
    };
});
<commit_msg>Fix Interference plugin lasso/dragNode<commit_after>/**
 * Created by nferon on 06/06/16.
 */
angular.module('sbAdminApp')
.directive('sigmaJs', function() {
    return {
        restrict: 'E',
        template: '

',
        scope: {
            graph: '=',
            width: '@',
            height: '@',
            id: '@',
            threshold: '@?',
            edgeLabels: '=?',
            locate: '=?',
            timeFilter: '=?',
            eventCatcher: '&',
            metricFilter: '@?',
            metricMinFilter: '@?',
            metricMaxFilter: '@?',
            metricNEutralFilter: '@?',
            interactor: '@?',
            cleanRefresh: '@?',
            s: '=?',
            lasso: '=?',
            maxNodeSize: '=?'
        },
        link: function (scope, element) {
            // default values
            if (scope.threshold == undefined)
                scope.threshold = 4;
            if (scope.edgeLabels == undefined)
                scope.edgeLabels = false;
            if (scope.metricMinFilter == undefined)
                scope.metricMinFilter = 1;
            if (scope.metricMaxFilter == undefined)
                scope.metricMaxFilter = 10;
            if (scope.metricNeutralFilter == undefined)
                scope.metricNeutralFilter = -1;
            var neighbourhood = {}
            neighbourhood.adjacentNodes = [];
            neighbourhood.adjacentEdges = [];
            neighbourhood.nodesColour = [];
            neighbourhood.edgesColour = [];

            // Create sigma instance
            var s = new sigma({
                renderer: {
                    container: element[0].firstChild,
                    type: 'canvas'
                },
                settings: {
                    labelThreshold: scope.threshold,
                    labelSize: "fixed",
                    drawEdgeLabels: scope.edgeLabels,
                    minArrowSize: 5,
                    enableEdgeHovering: false,
                    edgeHoverColor: '#000',
                    defaultEdgeHoverColor: '#000',
                    edgeHoverSizeRatio: 2,
                    edgeHoverExtremities: true,
                    maxNodeSize: scope.maxNodeSize
                }
            });
            scope.s = s;
            /**** Plugins ****/

            /**** Interactions ****/
            function arrayUnique(array) {
              var a = array.concat();
              for(var i=0; i<a.length; ++i) {
                for(var j=i+1; j<a.length; ++j) {
                  if(a[i] === a[j])
                    a.splice(j--, 1);
                }
              }
              return a;
            };
            function resetHighlight() {
              for(var tmp=0; tmp<neighbourhood.adjacentNodes.length ; tmp++) {
                neighbourhood.adjacentNodes[tmp].size/=2;
                neighbourhood.adjacentNodes[tmp].color = neighbourhood.nodesColour[tmp];
              }
              for(var tmp=0; tmp<neighbourhood.adjacentEdges.length ; tmp++) {
                neighbourhood.adjacentEdges[tmp].size/=2;
                neighbourhood.adjacentEdges[tmp].color=neighbourhood.edgesColour[tmp];
              }
              neighbourhood.adjacentNodes = [];
              neighbourhood.adjacentEdges = [];
            }
            function highlightNeighbourhood(e) {
              var node = e.data.node;
              resetHighlight();

              // Get adjacent nodes:
              neighbourhood.adjacentNodes = neighbourhood.adjacentNodes.concat(s.graph.adjacentNodes(node.id));

              // Add hovered nodes to the array and remove duplicates:
              neighbourhood.adjacentNodes = arrayUnique(neighbourhood.adjacentNodes.concat(node));

              // Get adjacent edges:
              neighbourhood.adjacentEdges = neighbourhood.adjacentEdges.concat(s.graph.adjacentEdges(node.id));

              // Remove duplicates:
              neighbourhood.adjacentEdges = arrayUnique(neighbourhood.adjacentEdges);
              for(var tmp=0; tmp<neighbourhood.adjacentNodes.length ; tmp++) {
                neighbourhood.adjacentNodes[tmp].size*=2;
                neighbourhood.nodesColour[tmp] = neighbourhood.adjacentNodes[tmp].color;
                neighbourhood.adjacentNodes[tmp].color="rgb(0,0,200)"
              }
              for(var tmp=0; tmp<neighbourhood.adjacentEdges.length ; tmp++) {
                neighbourhood.adjacentEdges[tmp].size*=2;
                neighbourhood.edgesColour[tmp] = neighbourhood.adjacentEdges[tmp].color;
                neighbourhood.adjacentEdges[tmp].color="rgb(0,0,200)"
              }
              s.refresh();
            }

            /**** Bind Event ****/
            s.bind('clickEdges hovers', function(e) {
                scope.eventCatcher()(e);
            });

            s.bind('clickNode', function(e) {
                scope.eventCatcher()(e);
                if (scope.interactor == "neighbourhood") {
                    highlightNeighbourhood(e);
                }
            });

            /**** locate ****/
            if (scope.locate != undefined) {
                var conf = {
                    animation: {
                        node: {duration: 800},
                        edge: {duration: 800},
                        center: {duration: 300}
                    },
                    focusOut: false,
                    zoomDef: 1
                };
                var locate = sigma.plugins.locate(s, conf);

                locate.setPadding({
                    top: 10,
                    bottom: 10,
                    right: 10,
                    left: 10
                });
                scope.$watch('locate', function (newVal, oldVal) {
                    if(newVal.toString() != oldVal.toString()) {
                        var nodes = s.graph.nodes().filter(function (n) {
                            if (n.user_id != undefined && scope.locate.indexOf(parseInt(n.user_id)) != -1) {
                                //todo use color code
                                n.color = "rgb(51,122,183)";
                                return true;
                            }
                            else if (n.post_id != undefined && scope.locate.indexOf(parseInt(n.post_id)) != -1) {
                                n.color = "rgb(92,184,92)";
                                return true;
                            }
                            else if (n.comment_id != undefined && scope.locate.indexOf(parseInt(n.comment_id)) != -1) {
                                n.color = "rgb(240, 173, 78)";
                                return true;
                            }
                            else
                                n.color = "rgb(128, 128, 128)";
                        }).map(function (n) {
                            return n.id;
                        });
                        if (nodes.length > 0)
                            locate.nodes(nodes);
                        else
                            locate.center(conf.zoomDef);
                    }
                });
            }

            /**** Filter ****/
            scope.$watch('timeFilter', function () {
                s.graph.nodes().filter(function (n) {
                    if (n.user_id != undefined && scope.timeFilter.start <= n.timestamp && scope.timeFilter.end >= n.timestamp) {
                        //todo use color code
                        n.color = "rgb(51,122,183)";
                        return true;
                    }
                    else if (n.post_id != undefined && scope.timeFilter.start <= n.timestamp && scope.timeFilter.end >= n.timestamp) {
                        //todo use color code
                        n.color = "rgb(92,184,92)";
                        return true;
                    }
                    else if  (n.comment_id != undefined && scope.timeFilter.start <= n.timestamp && scope.timeFilter.end >= n.timestamp) {
                        n.color = "rgb(240, 173, 78)";
                        return true;
                    }
                    else
                        n.color = "rgb(128, 128, 128)";
                }).map(function (n) {
                    return n.id;
                });
                s.refresh();
            });

            var filter = new sigma.plugins.filter(s);
            var metricFilter = function(metricMin, metricMax) {
                filter
                  .undo('nodeFilter')
                  .nodesBy(function(n) {
                    return (((Number(n[scope.metricFilter]) >= Number(metricMin) && Number(n[scope.metricFilter]) <= Number(metricMax)) || Number(n[scope.metricFilter]) == Number(scope.metricNeutralFilter)));
                  }, 'nodeFilter')
                  .apply();
                filter
                  .undo('edgeFilter')
                  .edgesBy(function(e) {
                    return (Number(e[scope.metricFilter]) >= Number(metricMin) && Number(e[scope.metricFilter]) <= Number(metricMax));
                  }, 'edgeFilter')
                  .apply();
            }

            /**** Watch for update ****/
            scope.$watch('metricMinFilter', function(newVal) {
                metricFilter(newVal, scope.metricMaxFilter);
            });
            scope.$watch('metricMaxFilter', function(newVal) {
                metricFilter(scope.metricMinFilter, newVal);
            });
            scope.$watch('graph', function() {
                if (scope.graph != undefined && scope.graph != []){
                  s.graph.clear();
                  s.graph.read(scope.graph);
                  if (s.graph.nodes().length > 0){
                    s.ready = true;
                  }
                  s.refresh();
                }

            });
            scope.$watch('edgeLabels', function(newVal) {
                if (scope.graph != undefined && scope.graph != []){
                  s.graph.clear();
                  s.settings({
                      drawEdgeLabels: newVal
                  });
                  s.graph.read(scope.graph);
                  s.refresh();
                }
            });
            scope.$watch('threshold', function(newVal) {
                if (scope.graph != undefined && scope.graph != []){
                  //s.graph.clear();
                  s.settings({
                      labelThreshold: newVal
                  });
                  //s.graph.read(scope.graph);
                  s.refresh();
                }
            });
            scope.$watch('width', function() {
                element.children().css("width",scope.width);
                s.refresh();
                window.dispatchEvent(new Event('resize')); //hack so that it will be shown instantly
            });
            scope.$watch('height', function() {
                element.children().css("height",scope.height);
                s.refresh();
                window.dispatchEvent(new Event('resize'));//hack so that it will be shown instantly
            });
            scope.$watch('cleanRefresh', function() {
                resetHighlight();
                s.refresh();
            });

            var lasso = new sigma.plugins.lasso(s, s.renderers[0], {
              'strokeStyle': 'black',
              'lineWidth': 2,
              'fillWhileDrawing': true,
              'fillStyle': 'rgba(41, 41, 41, 0.2)',
              'cursor': 'crosshair'
            });
            scope.lasso = lasso;

            scope.$watch('interactor', function() {
                if (scope.interactor == 'dragNode') {
                    //console.log('change to drag')
                    // Instanciate the ActiveState plugin:
                    var activeState = sigma.plugins.activeState(s);

                    if (s.dragListener){
                      delete s.dragListener;
                    }
                    s.dragListener = sigma.plugins.dragNodes(s, s.renderers[0], activeState);
                } else if (s.dragActive == true){
                    s.dragListener.unbindAll();
                }

                if (scope.interactor == 'lasso') {
                  lasso.activate();
                }
                else{
                  lasso.deactivate();
                }

                //console.log('change to lasso');
                if (scope.interactor == 'navigate') {
                    //console.log('change to navigate')
                }
                //var dragListener = new sigma.plugins.dragNodes(sigmaInstance, renderer, activeState);

            });

            element.on('$destroy', function() {
                s.graph.clear();
            });
        }
    };
});
<|endoftext|>


<commit_before>import Label from './Label';

// import { sum } from '../utils';

function LabelCluster(id, t = 1.0) {
  Label.call(this, id, t);
}

/* ------------------------------ Inheritance ------------------------------- */

LabelCluster.prototype = Object.create(Label.prototype);
LabelCluster.prototype.constructor = LabelCluster;

/* ------------------------------- Properties ------------------------------- */

function getDataPos() {
  return this.src.members.translate(member => member.getDataPosition());
}

Object.defineProperty(LabelCluster.prototype, 'dataPos', { get: getDataPos });

/* --------------------------------- Methods -------------------------------- */

LabelCluster.prototype.compDimPos = function compDimPos() {
  const size = this.src.size;

  // Update centroids
  this.x = this.src.members.reduce((sum, member) => sum + member.minX + member.maxX, 0) / 2 * size;
  this.y = this.src.members.reduce((sum, member) => sum + member.minY + member.maxY, 0) / 2 * size;
  this.oX = this.x;
  this.oY = this.y;

  // Update bounding area of the origins
  this.minX = this.src.members.reduce((a, member) => Math.min(a, member.minX), Infinity);
  this.maxX = this.src.members.reduce((a, member) => Math.max(a, member.maxX), -Infinity);
  this.minY = this.src.members.reduce((a, member) => Math.min(a, member.minY), Infinity);
  this.maxY = this.src.members.reduce((a, member) => Math.max(a, member.maxY), -Infinity);

  // Update width and height halfs of the bounding area of the origin
  this.oWH = (this.maxX - this.minX) / 2;
  this.oHH = (this.maxY - this.minY) / 2;

  return this;
};

LabelCluster.prototype.setDim = function setDim(width, height) {
  this.width = width;
  this.height = height;
  this.wH = width / 2;
  this.hH = height / 2;
  return this;
};

LabelCluster.prototype.setSrc = function setSrc(cluster) {
  this.src = cluster;
  this.compDimPos();
  return this;
};

export default LabelCluster;
<commit_msg>Fix calculation of the centroid<commit_after>import Label from './Label';

// import { sum } from '../utils';

function LabelCluster(id, t = 1.0) {
  Label.call(this, id, t);
}

/* ------------------------------ Inheritance ------------------------------- */

LabelCluster.prototype = Object.create(Label.prototype);
LabelCluster.prototype.constructor = LabelCluster;

/* ------------------------------- Properties ------------------------------- */

function getDataPos() {
  return this.src.members.translate(member => member.getDataPosition());
}

Object.defineProperty(LabelCluster.prototype, 'dataPos', { get: getDataPos });

/* --------------------------------- Methods -------------------------------- */

LabelCluster.prototype.compDimPos = function compDimPos() {
  const size = this.src.size;

  // Update centroids
  this.x = this.src.members
    .reduce((sum, member) => sum + member.minX + member.maxX, 0) / (2 * size);
  this.y = this.src.members
    .reduce((sum, member) => sum + member.minY + member.maxY, 0) / (2 * size);
  this.oX = this.x;
  this.oY = this.y;

  // Update bounding area of the origins
  this.minX = this.src.members
    .reduce((a, member) => Math.min(a, member.minX), Infinity);
  this.maxX = this.src.members
    .reduce((a, member) => Math.max(a, member.maxX), -Infinity);
  this.minY = this.src.members
    .reduce((a, member) => Math.min(a, member.minY), Infinity);
  this.maxY = this.src.members
    .reduce((a, member) => Math.max(a, member.maxY), -Infinity);

  // Update width and height halfs of the bounding area of the origin
  this.oWH = (this.maxX - this.minX) / 2;
  this.oHH = (this.maxY - this.minY) / 2;

  return this;
};

LabelCluster.prototype.setDim = function setDim(width, height) {
  this.width = width;
  this.height = height;
  this.wH = width / 2;
  this.hH = height / 2;
  return this;
};

LabelCluster.prototype.setSrc = function setSrc(cluster) {
  this.src = cluster;
  this.compDimPos();
  return this;
};

export default LabelCluster;
<|endoftext|>


<commit_before>'use strict';

var forEach = require('lodash/forEach');
var isEqual = require('lodash/isEqual');
var oo = require('../util/oo');
var TreeIndex = require('../util/TreeIndex');

function PathEventProxy(doc) {
  this.listeners = new TreeIndex.Arrays();
  this._list = [];
  this.doc = doc;
}

PathEventProxy.Prototype = function() {

  this.on = function(path, method, context) {
    this._add(context, path, method);
  };

  // proxy.off(this)
  // proxy.off(this, path)
  // proxy.off(this, path, this.onPropertyUpdate)
  this.off = function(context, path, method) {
    this._remove(context, path, method);
  };

  this.connect = function(listener, path, method) {
    console.warn('DEPRECATED: use proxy.on(path, this.onPropertyChange, this) instead');
    this.on(path, method, listener);
  };

  this.disconnect = function(listener) {
    console.warn('DEPRECATED: use proxy.off(this) instead');
    this.off(listener);
  };

  this.onDocumentChanged = function(change, info, doc) {
    // stop if no listeners registered
    if (this._list.length === 0) {
      return;
    }
    var listeners = this.listeners;
    forEach(change.updated, function(_, pathStr) {
      var scopedListeners = listeners.get(pathStr.split(','));
      forEach(scopedListeners, function(entry) {
        entry.method.call(entry.listener, change, info, doc);
      });
    });
  };

  this._add = function(listener, path, method) {
    if (!method) {
      throw new Error('Invalid argument: expected function but got ' + method);
    }
    var entry = { listener: listener, path: path, method: method };
    this.listeners.add(path, entry);
    this._list.push(entry);
  };

  this._remove = function(listener, path, method) {
    for (var i = 0; i < this._list.length; i++) {
      var item = this._list[i];
      var match = (
        (!path || isEqual(item.path, path)) &&
        (!listener || item.listener === listener) &&
        (!method || item.method !== method)
      );
      if (match) {
        var entry = this._list[i];
        this._list.splice(i, 1);
        this.listeners.remove(entry.path, entry);
      }
    }
  };

};

oo.initClass(PathEventProxy);

module.exports = PathEventProxy;
<commit_msg>Traverse PathEventProxy listeners more robustly.<commit_after>'use strict';

var forEach = require('lodash/forEach');
var isEqual = require('lodash/isEqual');
var isArray = require('lodash/isArray');
var oo = require('../util/oo');
var TreeIndex = require('../util/TreeIndex');

function PathEventProxy(doc) {
  this.listeners = new TreeIndex.Arrays();
  this._list = [];
  this.doc = doc;
}

PathEventProxy.Prototype = function() {

  this.on = function(path, method, context) {
    this._add(context, path, method);
  };

  // proxy.off(this)
  // proxy.off(this, path)
  // proxy.off(this, path, this.onPropertyUpdate)
  this.off = function(context, path, method) {
    this._remove(context, path, method);
  };

  this.connect = function(listener, path, method) {
    console.warn('DEPRECATED: use proxy.on(path, this.onPropertyChange, this) instead');
    this.on(path, method, listener);
  };

  this.disconnect = function(listener) {
    console.warn('DEPRECATED: use proxy.off(this) instead');
    this.off(listener);
  };

  this.onDocumentChanged = function(change, info, doc) {
    // stop if no listeners registered
    if (this._list.length === 0) {
      return;
    }
    var listeners = this.listeners;
    forEach(change.updated, function(_, pathStr) {
      var scopedListeners = listeners.get(pathStr.split(','));
      if (isArray(scopedListeners)) scopedListeners = scopedListeners.slice(0);
      forEach(scopedListeners, function(entry) {
        entry.method.call(entry.listener, change, info, doc);
      });
    });
  };

  this._add = function(listener, path, method) {
    if (!method) {
      throw new Error('Invalid argument: expected function but got ' + method);
    }
    var entry = { listener: listener, path: path, method: method };
    this.listeners.add(path, entry);
    this._list.push(entry);
  };

  this._remove = function(listener, path, method) {
    for (var i = 0; i < this._list.length; i++) {
      var item = this._list[i];
      var match = (
        (!path || isEqual(item.path, path)) &&
        (!listener || item.listener === listener) &&
        (!method || item.method !== method)
      );
      if (match) {
        var entry = this._list[i];
        this._list.splice(i, 1);
        this.listeners.remove(entry.path, entry);
      }
    }
  };

};

oo.initClass(PathEventProxy);

module.exports = PathEventProxy;
<|endoftext|>


<commit_before>/*global define*/

define(['jquery', 'underscore', 'backbone', 'templates', ], function($, _, Backbone, JST) {
    'use strict';
    var MILLIS_IN_SECOND = 1000,
        SECONDS_IN_MINUTE = 60,
        MINUTE_AS_MILLIS = MILLIS_IN_SECOND * SECONDS_IN_MINUTE,
        MINUTES_IN_HOUR = 60,
        HOURS_AS_MILLIS = MINUTE_AS_MILLIS * MINUTES_IN_HOUR,
        HOURS_IN_DAY = 24,
        DAY_AS_MILLIS = HOURS_AS_MILLIS * HOURS_IN_DAY,
        dateRegexp = new RegExp(/(\w+)\s+(\w+)\s+(\d+)\s+(\d{4})/),
        sourceRegexp = new RegExp(/>(.*)</);
    var TweetView = Backbone.View.extend({
        template: JST['app/scripts/templates/tweet.ejs'],
        initialize: function( /*opts*/ ) {
            _.bindAll(this, 'render');
        },
        millisToNearestUnit: function(ms) {
            var secs, minutes, hours, days;
            //console.log(ms + 'ms');
            if (ms >= DAY_AS_MILLIS) {
                days = ms / DAY_AS_MILLIS;
                days = Math.floor(hours);
                return days + 'd';
            }
            if (ms >= HOURS_AS_MILLIS) {
                hours = (ms / HOURS_AS_MILLIS) % HOURS_IN_DAY;
                hours = Math.floor(hours);
                return hours + 'h';
            }
            if (ms >= MINUTE_AS_MILLIS) {
                minutes = (ms / MINUTE_AS_MILLIS) % MINUTES_IN_HOUR;
                minutes = Math.floor(minutes);
                return minutes + 'm';
            }
            if (ms >= MILLIS_IN_SECOND) {
                secs = (ms / MILLIS_IN_SECOND) % SECONDS_IN_MINUTE;
                secs = Math.floor(secs);
                return secs + 's';
            }
            return 'Just Now';
        },
        render: function() {
            var json = this.model.toJSON();
            var date = new Date(json.created_at);
            var created = date.getTime();
            var now = Date.now();
            json.timesince = this.millisToNearestUnit(now - created);
            var timeStr = date.toLocaleTimeString().replace(/:\d{2} /, ' ');
            var dateArr = dateRegexp.exec(date.toDateString());
            json.created_at = timeStr + ' - ' + dateArr[3] + ' ' + dateArr[2] + ' ' + dateArr[4].substr(2, 4);
            // replace single quotes with html entity so you can use
            // it in title. Should also prevent escaping.
            json.text = json.text.replace(/'/g, ''');
            json.source = sourceRegexp.exec(json.source)[1];
            this.$el.html(this.template(json));
        }
    });
    var TweetAreaView = Backbone.View.extend({
        el: '.tweet-area',
        model: TweetView,
        removeCount: 5,
        viewModel: {},
        initialize: function(opts) {
            if (this.collection) {
                this.listenTo(this.collection, 'add', this.add, this);
            }
            if (opts && opts.addClazz) {
                this.addClazz = opts.addClazz;
            }
        },
        add: function(model) {
            var d = $.Deferred();
            var $tweets = this.$('.tweet');
            if ($tweets.length) {
                $tweets.addClass('slide-down1 animate0');
                var fn = function(el) {
                        d.resolve(el);
                    };
                $tweets[0].addEventListener('webkitAnimationEnd', fn);
                $tweets[0].addEventListener('animationend', fn);
            } else {
                d.resolve();
            }
            var p = d.promise();
            var self = this;
            p.done(function() {
                $tweets.removeClass('slide-down1 animate0');
                var view = new self.model({
                    model: model
                });
                self.viewModel[model.id] = view;
                view.render();
                var $view = view.$el;
                $view.css({
                    visibility: 'hidden'
                });
                self.$el.prepend($view);
                var fn = function() {
                        $view.removeClass('animate0 reveal-left reveal-right');
                    };
                $view[0].addEventListener('animationend', fn);
                $view[0].addEventListener('webkitAnimationEnd', fn);
                $view.css({
                    visibility: 'visible'
                }).addClass(self.addClazz + ' animate0');
                setTimeout(function() {
                    var len = self.$('.tweet').length;
                    if (self.$('.tweet').length < self.removeCount) {
                        return;
                    }
                    var id = $tweets.last().attr('data-id');
                    var view = self.viewModel[id];
                    console.log('at' + len + 'removing ' + id);
                    view.remove();
                    view.model = null;
                    delete self.viewModel[id];
                }, 0);
            });
        }
    });

    return {
        TweetView: TweetView,
        TweetAreaView: TweetAreaView
    };
});
<commit_msg>Refactor for maintainability<commit_after>/*global define*/

define(['jquery', 'underscore', 'backbone', 'templates', ], function($, _, Backbone, JST) {
    'use strict';
    var MILLIS_IN_SECOND = 1000,
        SECONDS_IN_MINUTE = 60,
        MINUTE_AS_MILLIS = MILLIS_IN_SECOND * SECONDS_IN_MINUTE,
        MINUTES_IN_HOUR = 60,
        HOURS_AS_MILLIS = MINUTE_AS_MILLIS * MINUTES_IN_HOUR,
        HOURS_IN_DAY = 24,
        DAY_AS_MILLIS = HOURS_AS_MILLIS * HOURS_IN_DAY,
        dateRegexp = new RegExp(/(\w+)\s+(\w+)\s+(\d+)\s+(\d{4})/),
        sourceRegexp = new RegExp(/>(.*)</);
    var TweetView = Backbone.View.extend({
        template: JST['app/scripts/templates/tweet.ejs'],
        initialize: function( /*opts*/ ) {
            _.bindAll(this, 'render');
        },
        millisToNearestUnit: function(ms) {
            var secs, minutes, hours, days;
            //console.log(ms + 'ms');
            if (ms >= DAY_AS_MILLIS) {
                days = ms / DAY_AS_MILLIS;
                days = Math.floor(hours);
                return days + 'd';
            }
            if (ms >= HOURS_AS_MILLIS) {
                hours = (ms / HOURS_AS_MILLIS) % HOURS_IN_DAY;
                hours = Math.floor(hours);
                return hours + 'h';
            }
            if (ms >= MINUTE_AS_MILLIS) {
                minutes = (ms / MINUTE_AS_MILLIS) % MINUTES_IN_HOUR;
                minutes = Math.floor(minutes);
                return minutes + 'm';
            }
            if (ms >= MILLIS_IN_SECOND) {
                secs = (ms / MILLIS_IN_SECOND) % SECONDS_IN_MINUTE;
                secs = Math.floor(secs);
                return secs + 's';
            }
            return 'Just Now';
        },
        toJSON: function(model) {
            var json = model.toJSON();
            var date = new Date(json.created_at);
            var createdMs = date.getTime();
            var nowMs = Date.now();
            json.timesince = this.millisToNearestUnit(nowMs - createdMs);
            var timeStr = date.toLocaleTimeString().replace(/:\d{2} /, ' ');
            var dateArr = dateRegexp.exec(date.toDateString());
            json.created_at = timeStr + ' - ' + dateArr[3] + ' ' + dateArr[2] + ' ' + dateArr[4].substr(2, 4);
            // replace single quotes with html entity so you can use
            // it in title. Should also prevent escaping.
            json.text = json.text.replace(/'/g, ''');
            json.source = sourceRegexp.exec(json.source)[1];
            return json;
        },
        render: function() {
            var json = this.toJSON(this.model);
            this.$el.html(this.template(json));
        }
    });
    var TweetAreaView = Backbone.View.extend({
        el: '.tweet-area',
        itemView: TweetView,
        removeCount: 5,
        initialize: function(opts) {
            this.viewModel = {};
            if (this.collection) {
                this.listenTo(this.collection, 'add', this.add, this);
            }
            if (opts && opts.addClazz) {
                this.addClazz = opts.addClazz;
            }
            _.bindAll(this, 'animateInsertion', 'cleanUpOldTweetViews');
        },
        animateInsertion: function($tweets, model) {
            $tweets.removeClass('slide-down1 animate0');
            var view = new this.itemView({
                model: model
            });
            this.viewModel[model.id] = view;
            view.render();
            var $view = view.$el;
            $view.css({
                visibility: 'hidden'
            });
            this.$el.prepend($view);
            var fn = function() {
                    $view.removeClass('animate0 reveal-left reveal-right');
                };
            $view[0].addEventListener('animationend', fn);
            $view[0].addEventListener('webkitAnimationEnd', fn);
            $view.css({
                visibility: 'visible'
            }).addClass(this.addClazz + ' animate0');
            setTimeout(this.cleanUpOldTweetViews, 0);
        },
        cleanUpOldTweetViews: function() {
            var $tweets = this.$('.tweet');
            var len = $tweets.length;
            if (len < this.removeCount) {
                return;
            }
            var id = $tweets.last().attr('data-id');
            var view = this.viewModel[id];
            //console.log('at ' + len + ' removing ' + id);
            view.remove();
            view.model = null;
            delete this.viewModel[id];
        },
        add: function(model) {
            var d = $.Deferred();
            var $tweets = this.$('.tweet');
            if ($tweets.length) {
                $tweets.addClass('slide-down1 animate0');
                var fn = function(el) {
                        d.resolve(el);
                    };
                $tweets[0].addEventListener('webkitAnimationEnd', fn);
                $tweets[0].addEventListener('animationend', fn);
            } else {
                d.resolve();
            }
            var p = d.promise();
            var self = this;
            p.done(function() {
                self.animateInsertion($tweets, model);
            });
        }
    });

    return {
        TweetView: TweetView,
        TweetAreaView: TweetAreaView
    };
});
<|endoftext|>


<commit_before>// Put all Config parameters here under module.exports and access then using MY.config
module.exports = {

	serverHost : "http://localhost",
	serverPort : 8888,

	appMode : "fast", // "fast" or "slow"

	baseUrl : "",

	log : {
		/*
		 * MY_LOGMODE_NONE     = 0,
		 * MY_LOGMODE_ERROR    = 0x01,
		 * MY_LOGMODE_WARN     = 0x02,
		 * MY_LOGMODE_IMP_DATA = 0x04,
		 * MY_LOGMODE_INFO     = 0x08,
		 * MY_LOGMODE_VERBOSE  = 0x10;
		 * Can have combination of above values to enable and disable
		 */
		level 				: (0x01 | 0x02 | 0x04 | 0x08),
		fileName 			: 'debug.log',
		exceptionFileName	: 'exceptions.log'
	}
};<commit_msg>Update config.js<commit_after>// Put all Config parameters here under module.exports and access then using MY.config
module.exports = {

	serverHost : "http://localhost",
	serverPort : 8888,

	appMode : "fast", // "fast" or "slow"

	delayEveryRequest : 0, // Value in milliseconds

	baseUrl : "",

	log : {
		/*
		 * MY_LOGMODE_NONE     = 0,
		 * MY_LOGMODE_ERROR    = 0x01,
		 * MY_LOGMODE_WARN     = 0x02,
		 * MY_LOGMODE_IMP_DATA = 0x04,
		 * MY_LOGMODE_INFO     = 0x08,
		 * MY_LOGMODE_VERBOSE  = 0x10;
		 * Can have combination of above values to enable and disable
		 */
		level 				: (0x01 | 0x02 | 0x04 | 0x08),
		fileName 			: 'debug.log',
		exceptionFileName	: 'exceptions.log'
	}
};
<|endoftext|>


<commit_before>/*global sap */

sap.ui.define([], function () {
    "use strict";

    /**
     * @name        nl.qualiture.plunk.demo.utils.Validator
     *
     * @class
     * @classdesc   Validator class.

     *
     * @version     Oktober 2015
     * @author      Robin van het Hof
     */
    var Validator = function() {
    	  this._isValid               = true;
		    this._isValidationPerformed = false;
    };

    /**
     * Returns true _only_ when the form validation has been performed, and no validation errors were found
     * @memberof nl.qualiture.plunk.demo.utils.Validator
     *
     * @returns {boolean}
     */
  	Validator.prototype.isValid = function() {
  		  return this._isValidationPerformed && this._isValid;
  	};

    /**
     * Recursively validates the given oControl and any aggregations (i.e. child controls) it may have
     * @memberof nl.qualiture.plunk.demo.utils.Validator
     *
     * @param {(sap.ui.core.Control|sap.ui.layout.form.FormContainer|sap.ui.layout.form.FormElement)} oControl - The control or element to be validated.
     */
	  Validator.prototype.validate = function(oControl) {
        var aPossibleAggregations = ["items", "content", "form", "formContainers", "formElements", "fields"],
            aControlAggregation   = null,
            aValidateProperties   = ["value", "selectedKey", "text"], // yes, I want to validate Select and Text controls too
            isValidatedControl    = false,
            i, j;

        // only validate controls and elements which have a 'visible' property
        if (oControl instanceof sap.ui.core.Control ||
            oControl instanceof sap.ui.layout.form.FormContainer ||
            oControl instanceof sap.ui.layout.form.FormElement) {

            // only check visible controls (invisible controls make no sense checking)
            if (oControl.getVisible()) {

                // check control for any properties worth validating
                for (i=0; i<aValidateProperties.length; i+=1) {
                    if (oControl.getBinding(aValidateProperties[i])) {
                    	// check if a data type exists (which may have validation constraints)
                        if (oControl.getBinding(aValidateProperties[i]).oType) {
                        	// try validating the bound value
                            try {
                                oControl.getBinding(aValidateProperties[i]).oType.validateValue(oControl.getProperty(aValidateProperties[i]));
                            }
                            // catch any validation errors
                            catch (ex) {
                                this._isValid = false;

                                oControl.setValueState("Error");
                                oControl.setValueStateText(ex.message);
                            }

                            isValidatedControl = true;
                        }
                    }
                }

                // if the control could not be validated, it may have aggregations
                if (!isValidatedControl) {
                    for (i=0; i<aPossibleAggregations.length; i+=1) {
                        aControlAggregation = oControl.getAggregation(aPossibleAggregations[i]);

                        if (aControlAggregation) {
                            // generally, aggregations are of type Array
                            if (aControlAggregation instanceof Array) {
                                for (j=0; j<aControlAggregation.length; j+=1) {
                                    this.validate(aControlAggregation[j]);
                                }
                            }
                            // ...however, with sap.ui.layout.form.Form, it is a single object *sigh*
                            else {
                                this.validate(aControlAggregation);
                            }
                        }
                    }
                }
            }
        }
        this._isValidationPerformed = true;
	  };

	  return Validator;
});<commit_msg>Reformatted<commit_after>/*global sap */

sap.ui.define([], function() {
    "use strict";

    /**
     * @name        nl.qualiture.plunk.demo.utils.Validator
     *
     * @class
     * @classdesc   Validator class.

     *
     * @version     Oktober 2015
     * @author      Robin van het Hof
     */
    var Validator = function() {
        this._isValid = true;
        this._isValidationPerformed = false;
    };

    /**
     * Returns true _only_ when the form validation has been performed, and no validation errors were found
     * @memberof nl.qualiture.plunk.demo.utils.Validator
     *
     * @returns {boolean}
     */
    Validator.prototype.isValid = function() {
        return this._isValidationPerformed && this._isValid;
    };

    /**
     * Recursively validates the given oControl and any aggregations (i.e. child controls) it may have
     * @memberof nl.qualiture.plunk.demo.utils.Validator
     *
     * @param {(sap.ui.core.Control|sap.ui.layout.form.FormContainer|sap.ui.layout.form.FormElement)} oControl - The control or element to be validated.
     */
    Validator.prototype.validate = function(oControl) {
        var aPossibleAggregations = ["items", "content", "form", "formContainers", "formElements", "fields"],
            aControlAggregation   = null,
            aValidateProperties   = ["value", "selectedKey", "text"], // yes, I want to validate Select and Text controls too
            isValidatedControl    = false,
            i, j;

        // only validate controls and elements which have a 'visible' property
        if (oControl instanceof sap.ui.core.Control ||
            oControl instanceof sap.ui.layout.form.FormContainer ||
            oControl instanceof sap.ui.layout.form.FormElement) {

            // only check visible controls (invisible controls make no sense checking)
            if (oControl.getVisible()) {

                // check control for any properties worth validating
                for (i = 0; i < aValidateProperties.length; i += 1) {
                    if (oControl.getBinding(aValidateProperties[i])) {
                        // check if a data type exists (which may have validation constraints)
                        if (oControl.getBinding(aValidateProperties[i]).oType) {
                            // try validating the bound value
                            try {
                                oControl.getBinding(aValidateProperties[i]).oType.validateValue(oControl.getProperty(aValidateProperties[i]));
                            }
                            // catch any validation errors
                            catch (ex) {
                                this._isValid = false;

                                oControl.setValueState("Error");
                                oControl.setValueStateText(ex.message);
                            }

                            isValidatedControl = true;
                        }
                    }
                }

                // if the control could not be validated, it may have aggregations
                if (!isValidatedControl) {
                    for (i = 0; i < aPossibleAggregations.length; i += 1) {
                        aControlAggregation = oControl.getAggregation(aPossibleAggregations[i]);

                        if (aControlAggregation) {
                            // generally, aggregations are of type Array
                            if (aControlAggregation instanceof Array) {
                                for (j = 0; j < aControlAggregation.length; j += 1) {
                                    this.validate(aControlAggregation[j]);
                                }
                            }
                            // ...however, with sap.ui.layout.form.Form, it is a single object *sigh*
                            else {
                                this.validate(aControlAggregation);
                            }
                        }
                    }
                }
            }
        }
        this._isValidationPerformed = true;
    };

    return Validator;
});
<|endoftext|>


<commit_before>var GitHubAPI = require('github');

var github = new GitHubAPI({
    version: "3.0.0",
    protocol: "https",
    timeout: 5000
});

function getRepoInformation(username, reponame, cb) {
    github.repos.get({
        user: username,
        repo: reponame
    }, function(err, res) {
        if (err) {
            cb("Github response is dicked");
        } else {
            var name = res.name,
                description = res.description,
                language = res.language,
                forks = "Forks: " + res.forks_count;
            if (!language)
                language = "";
            else
                language = "| " + language;
            if (description.length > 60)
                description = description.substr(0, 40);

            cb(name, '| ', description, language, '| ', forks);
        }
    });
}

var githubURLRegexes = [
    {
        name: "User",
        regex: /github\.com\/(\w+)\/?$/,
        getMessage: function(username, cb) {
            github.user.getFrom({
                user: username
            }, function(err, res) {
                if (err) {
                    cb("Github response is dicked");
                } else {
                    var followers = res.followers,
                        following = res.following,
                        publicRepose = res.public_repos,
                        company = res.company;
                        if (!company) company = "";
                        else company = "Works for " + company + " |";

                    cb(username + ":", company, "Followers:",
                        followers, "| Following:", following);
                }
            });
        }
    },
    {
        name: "Repo",
        regex: /github\.com\/(\w+)\/((?:\w|-)+)\/?$/,
        getMessage: getRepoInformation
    },
    {
        name: "Commit",
        regex: /github\.com\/(\w+)\/((?:\w|-)+)\/commit\/(\w+)\/?$/,
        getMessage: function(username, reponame, commit, cb) {
            github.repos.getCommit({
                user: username,
                repo: reponame,
                sha: commit
            }, function(err, res) {
                if (err) {
                    cb("Github response is dicked");
                } else {
                    var committer = res.committer.login,
                        message = res.commit.message;
                    // Extract first line of commit message
                    message = /.*/.exec(message);
                    if (message) message = message[0];
                    else message = "";
                    if (message.length > 50) message = message.substr(0, 50);

                    cb("Commit to", reponame, "by", committer + ":", message);
                }
            });
        }
    },
    {
        name: "Issue",
        regex: /github\.com\/(\w+)\/((?:\w|-)+)\/issues\/(\d+)\/?$/,
        getMessage: function(username, reponame, issue, cb) {
            github.issues.getRepoIssue({
                user: username,
                repo: reponame,
                number: issue
            }, function(err, res) {
                if (err) {
                    cb("Github response is dicked");
                } else {
                    var title = res.title,
                        state = res.state;
                    cb("Issue for", reponame + ":", title, "|", state);
                }
            });
        }
    },
    {
        name: "IO Page",
        regex: /\/\/(\w+)\.github\.io\/((?:\w|-)+)\/?/,
        getMessage: getRepoInformation
    }
];

module.exports.url = function(url, reply) {
    var matchInfo, githubObj;
    for (var i = 0; i < githubURLRegexes.length; i++) {
        var obj = githubURLRegexes[i];
        if ((matchInfo = obj.regex.exec(url))) {
            githubObj = obj;
            break;
        }
    }
    if (githubObj) {
        matchInfo.shift();
        matchInfo.push(reply);
        githubObj.getMessage.apply(githubObj, matchInfo);
    }
}
<commit_msg>Better support for github links<commit_after>var GitHubAPI = require('github'),
    _ = require('underscore');

var github = new GitHubAPI({
    version: "3.0.0",
    protocol: "https",
    timeout: 5000
});

function getRepoInformation(username, reponame, cb) {
    github.repos.get({
        user: username,
        repo: reponame
    }, function(err, res) {
        if (err) {
            cb("Github response is dicked");
        } else {
            var name = res.name,
                description = res.description,
                language = res.language,
                forks = "Forks: " + res.forks_count;
            if (!language)
                language = "";
            else
                language = "| " + language;
            if (description.length > 60)
                description = description.substr(0, 40);

            cb(name, '| ', description, language, '| ', forks);
        }
    });
}

var githubURLRegexes = [
    {
        name: "User",
        regex: /github\.com\/(\w+)\/?/,
        getMessage: function(username, cb) {
            github.user.getFrom({
                user: username
            }, function(err, res) {
                if (err) {
                    cb("Github response is dicked");
                } else {
                    var followers = res.followers,
                        following = res.following,
                        publicRepose = res.public_repos,
                        company = res.company;
                        if (!company) company = "";
                        else company = "Works for " + company + " |";

                    cb(username + ":", company, "Followers:",
                        followers, "| Following:", following);
                }
            });
        }
    },
    {
        name: "Repo",
        regex: /github\.com\/(\w+)\/((?:\w|-)+)\/?/,
        getMessage: getRepoInformation
    },
    {
        name: "Commit",
        regex: /github\.com\/(\w+)\/((?:\w|-)+)\/commit\/(\w+)\/?/,
        getMessage: function(username, reponame, commit, cb) {
            github.repos.getCommit({
                user: username,
                repo: reponame,
                sha: commit
            }, function(err, res) {
                if (err) {
                    cb("Github response is dicked");
                } else {
                    var committer = res.committer.login,
                        message = res.commit.message;
                    // Extract first line of commit message
                    message = /.*/.exec(message);
                    if (message) message = message[0];
                    else message = "";
                    if (message.length > 50) message = message.substr(0, 50);

                    cb("Commit to", reponame, "by", committer + ":", message);
                }
            });
        }
    },
    {
        name: "Issue",
        regex: /github\.com\/(\w+)\/((?:\w|-)+)\/issues\/(\d+)\/?/,
        getMessage: function(username, reponame, issue, cb) {
            github.issues.getRepoIssue({
                user: username,
                repo: reponame,
                number: issue
            }, function(err, res) {
                if (err) {
                    cb("Github response is dicked");
                } else {
                    var title = res.title,
                        state = res.state;
                    cb("Issue for", reponame + ":", title, "|", state);
                }
            });
        }
    },
    {
        name: "IO Page",
        regex: /\/\/(\w+)\.github\.io\/((?:\w|-)+)\/?/,
        getMessage: getRepoInformation
    }
];

module.exports.url = function(url, reply) {
    var matchInfo, githubObj;
    var matched = [];
    for (var i = 0; i < githubURLRegexes.length; i++) {
        var obj = githubURLRegexes[i];
        if ((matchInfo = obj.regex.exec(url))) {
            githubObj = obj;
            matched.push({
                githubObj: githubObj,
                matchInfo: matchInfo
            });
        }
    }
    if (matched.length) {
        /* Find the longest URL that was matched, that way subdirs get matched
         * to the most applicable parent dir.
         */
        var sorted = _.sortBy(matched, function(match) {
            return -match.matchInfo[0].length;
        });
        matchInfo = sorted[0].matchInfo;
        githubObj = sorted[0].githubObj;

        matchInfo.shift();
        matchInfo.push(reply);
        githubObj.getMessage.apply(githubObj, matchInfo);
    }
}
<|endoftext|>


<commit_before>/*
 * The webapp module provides support for building web applications in Helma NG.
 */

// import modules
require('core/string');

include('helma/webapp/request');
include('helma/webapp/response');

import('helma/system', 'system');
import('helma/httpserver', 'server');
import('helma/logging', 'logging');

export('start', 'stop', 'getConfig', 'handleRequest', 'error', 'notfound');

var log = logging.getLogger(__name__);

var __shared__ = true;

/**
 * Handler function called by the Jack servlet.
 *
 * @param env the jack environment argument
 */
function handleRequest(env) {
    // get config and apply it to req, res
    var config = getConfig();
    if (log.debugEnabled) log.debug('got config: ' + config.toSource());

    var req = new Request(env);
    var res = null;
    // set up jack env, request and config properties in per-request env module
    var webenv = require('helma/webapp/env');
    webenv.init(env, req, config);

    req.charset = config.charset || 'utf8';

    // resolve path and invoke action
    var path = req.path;
    if (path.startsWith('/')) {
        // strip leading slash
        path = path.slice(1);
    }

    function getPattern(spec) {
        var pattern = spec[0];
        if (typeof pattern == "string") {
            if (pattern.startsWith("/"))
                pattern = pattern.replace("/", "^");
            pattern = spec[0] = new RegExp(pattern);
        } else if (!(pattern instanceof RegExp)) {
            throw Error("Pattern must be a regular expression or string");
        }
        return pattern;
    }

    function getModule(spec, prefix) {
        var module = spec[1];
        if (typeof module == "string") {
            module = require(prefix + module);
        } else if (!(module instanceof Object)) {
            throw Error("Module must be a string or object");
        }
        return module;
    }

    function getAction(module, name) {
        name = name || "index";
        var action = module[name.replace(/\./g, "_")];
        if (typeof action == "function") {
            return action;
        }
        return null;
    }

    function resolveInConfig(config, path, prefix) {
        if (log.isDebugEnabled) log.debug('resolving path ' + path);
        if (config.urls instanceof Array) {
            var urls = config.urls;
            for (var i = 0; i < urls.length; i++) {
                if (log.isDebugEnabled) log.debug("checking url line: " + urls[i]);
                var match = getPattern(urls[i]).exec(path);
                if (log.isDebugEnabled) log.debug("got match: " + match);
                if (match != null) {
                    var module = getModule(urls[i], prefix);
                    if (log.isDebugEnabled) log.debug("module: " + module);
                    // cut matching prefix from path
                    path = path.substring(match[0].length);
                    // remove leading and trailing slashes
                    path = path.replace(/^\/+|\/+$/g, "");
                    //split
                    var pathArray = path.split(/\/+/);
                    var action = getAction(module, pathArray[0]);
                    // log.debug("got action: " + action);
                    if (typeof action == "function" && pathArray.length <= action.length) {
                        // add remaining path elements as additional action arguments
                        var actionArgs = pathArray.slice(1);
                        var matchedArgs = match.slice(1);
                        var args = [req].concat(matchedArgs).concat(actionArgs);
                        var middleware = config.middleware;
                        var middlewareIndex = 0;
                        // set up middleware chain in request object
                        req.process = function() {
                            if (middlewareIndex < middleware.length) {
                                return invokeMiddleware(middleware[middlewareIndex++], [req]);
                            } else {
                                return action.apply(module, args);
                            }
                        }
                        return req.process();
                    } else if (module.urls instanceof Array) {
                        // nested app
                        return resolveInConfig(module, path, match[0] + "/");
                    } else {
                        break;
                    }
                }
            }
        }
    }

    try {
        res = resolveInConfig(config, decodeURI(path), "");
    } catch (e) {
        if (e.retry) {
            throw e;
        } else if (e.redirect) {
            return new RedirectResponse(e.redirect);
        } else {
            res = error(req, e);
        }
    } finally {
        // TODO
        if (!res)
            res = notfound(req);
        if (!(res instanceof Array) && res.close)
            res = res.close();
    }
    return res;
}

function invokeMiddleware(middleware, args) {
    var functionName = 'handleRequest';
    var dot = middleware.indexOf('.');
    if (dot > -1) {
        functionName = middleware.substring(dot + 1);
        middleware = middleware.substring(0, dot);
    }
    var module = require(middleware);
    if (typeof module[functionName] !== 'function') {
        throw new Error('Middleware function ' + functionName + ' is not defined in ' + middleware);
    }
    log.debug('invoking middleware: ' + middleware);
    return module[functionName].apply(module, args);
}

/**
 * Standard error page
 * @param e the error that happened
 */
function error(req, e) {
    var res = new Response();
    res.status = 500;
    res.contentType = 'text/html';
    res.writeln('
', e, '
');
    if (e.fileName && e.lineNumber) {
        res.writeln('
In file', e.fileName, 'at line', e.lineNumber, '
');
    }
    if (e.rhinoException) {
        res.writeln('
Script Stack
');
        res.writeln('
', e.rhinoException.scriptStackTrace, '
');
        res.writeln('
Java Stack
');
        var writer = new java.io.StringWriter();
        var printer = new java.io.PrintWriter(writer);
        e.rhinoException.printStackTrace(printer);
        res.writeln('
', writer.toString(), '
');
        log.error(e.toString(), e.rhinoException);
    } else {
        log.error(e.toString());
    }
    return res.close();
}

/**
 * Standard notfound page
 */
function notfound(req) {
    var res = new Response();
    res.status = 404;
    res.contentType = 'text/html';
    res.writeln('
Not Found
');
    res.writeln('The requested URL', req.pathDecoded, 'was not found on the server.');
    return res.close();
}

/**
 * Try to load the configuration module.
 * @param configModuleName optional module name, default is 'config'
 */
function getConfig(configModuleName) {
    configModuleName = configModuleName || 'config';
    var config;
    try {
        config = require(configModuleName);
    } catch (noConfig) {
        log.info('Couldn\'t load config module: ' + noConfig);
    }
    return config || {};
}

/**
 * Start the jetty server.
 */
function start(config) {
    // start jetty http server
    config = config || getConfig();
    var httpConfig = config.httpConfig;
    server.start(httpConfig);
}

/**
 * Stop the jetty server.
 */
function stop() {
    // stop jetty HTTP server
    server.stop();
}

if (__name__ == '__main__') {
    for (var i = 1; i < system.args.length; i++) {
        var arg = system.args[i];
        if (arg.indexOf('-') == 0) {
            break;
        }
        system.addRepository(arg);
    }
    log.info('Setup module search: ' + system.getRepositories());
    start();
}
<commit_msg>Implement req.actionPath as the (canonical) part of the request path resolving to the current action. This makes default actions explicit ("/" -> "/index") and is thus usable for composing paths containing extra arguments.<commit_after>/*
 * The webapp module provides support for building web applications in Helma NG.
 */

// import modules
require('core/string');

include('helma/webapp/request');
include('helma/webapp/response');

import('helma/system', 'system');
import('helma/httpserver', 'server');
import('helma/logging', 'logging');

export('start', 'stop', 'getConfig', 'handleRequest', 'error', 'notfound');

var log = logging.getLogger(__name__);

var __shared__ = true;

/**
 * Handler function called by the Jack servlet.
 *
 * @param env the jack environment argument
 */
function handleRequest(env) {
    // get config and apply it to req, res
    var config = getConfig();
    if (log.debugEnabled) log.debug('got config: ' + config.toSource());

    var req = new Request(env);
    var res = null;
    // set up jack env, request and config properties in per-request env module
    var webenv = require('helma/webapp/env');
    webenv.init(env, req, config);

    req.charset = config.charset || 'utf8';

    // resolve path and invoke action
    var path = req.path;
    if (path.startsWith('/')) {
        // strip leading slash
        path = path.slice(1);
    }
    // used to compose req.actionPath, which is the part of the path
    // that resolves to the action (req.path minus argument elements)
    var actionPath = ["/"];

    function getPattern(spec) {
        var pattern = spec[0];
        if (typeof pattern == "string") {
            if (pattern.startsWith("/"))
                pattern = pattern.replace("/", "^");
            pattern = spec[0] = new RegExp(pattern);
        } else if (!(pattern instanceof RegExp)) {
            throw Error("Pattern must be a regular expression or string");
        }
        return pattern;
    }

    function getModule(spec, prefix) {
        var module = spec[1];
        if (typeof module == "string") {
            module = require(prefix + module);
        } else if (!(module instanceof Object)) {
            throw Error("Module must be a string or object");
        }
        return module;
    }

    function getAction(module, name) {
        name = name || "index";
        var action = module[name.replace(/\./g, "_")];
        if (typeof action == "function") {
            return action;
        }
        return null;
    }

    function resolveInConfig(config, path, prefix) {
        if (log.isDebugEnabled) log.debug('resolving path ' + path);
        if (config.urls instanceof Array) {
            var urls = config.urls;
            for (var i = 0; i < urls.length; i++) {
                if (log.isDebugEnabled) log.debug("checking url line: " + urls[i]);
                var match = getPattern(urls[i]).exec(path);
                if (log.isDebugEnabled) log.debug("got match: " + match);
                if (match != null) {
                    var module = getModule(urls[i], prefix);
                    if (log.isDebugEnabled) log.debug("module: " + module);
                    // cut matching prefix from path
                    path = path.substring(match[0].length);
                    // remove leading and trailing slashes
                    path = path.replace(/^\/+|\/+$/g, "");
                    //split
                    var pathArray = path.split(/\/+/);
                    var action = getAction(module, pathArray[0]);
                    // log.debug("got action: " + action);
                    if (typeof action == "function" && pathArray.length <= action.length) {
                        // set req.actionPath to the part of the path that resolves to the action
                        actionPath.push(match[0], pathArray[0] || "index");
                        req.actionPath =  actionPath.join("/").replace(/\/+/g, "/");
                        print(req.actionPath);
                        // add remaining path elements as additional action arguments
                        var actionArgs = pathArray.slice(1);
                        var matchedArgs = match.slice(1);
                        var args = [req].concat(matchedArgs).concat(actionArgs);
                        var middleware = config.middleware;
                        var middlewareIndex = 0;
                        // set up middleware chain in request object
                        req.process = function() {
                            if (middlewareIndex < middleware.length) {
                                return invokeMiddleware(middleware[middlewareIndex++], [req]);
                            } else {
                                return action.apply(module, args);
                            }
                        }
                        return req.process();
                    } else if (module.urls instanceof Array) {
                        // nested app
                        actionPath.push(match[0]);
                        return resolveInConfig(module, path, match[0]);
                    } else {
                        break;
                    }
                }
            }
        }
    }

    try {
        res = resolveInConfig(config, decodeURI(path), "");
    } catch (e) {
        if (e.retry) {
            throw e;
        } else if (e.redirect) {
            return new RedirectResponse(e.redirect);
        } else {
            res = error(req, e);
        }
    } finally {
        // TODO
        if (!res)
            res = notfound(req);
        if (!(res instanceof Array) && res.close)
            res = res.close();
    }
    return res;
}

function invokeMiddleware(middleware, args) {
    var functionName = 'handleRequest';
    var dot = middleware.indexOf('.');
    if (dot > -1) {
        functionName = middleware.substring(dot + 1);
        middleware = middleware.substring(0, dot);
    }
    var module = require(middleware);
    if (typeof module[functionName] !== 'function') {
        throw new Error('Middleware function ' + functionName + ' is not defined in ' + middleware);
    }
    log.debug('invoking middleware: ' + middleware);
    return module[functionName].apply(module, args);
}

/**
 * Standard error page
 * @param e the error that happened
 */
function error(req, e) {
    var res = new Response();
    res.status = 500;
    res.contentType = 'text/html';
    res.writeln('
', e, '
');
    if (e.fileName && e.lineNumber) {
        res.writeln('
In file', e.fileName, 'at line', e.lineNumber, '
');
    }
    if (e.rhinoException) {
        res.writeln('
Script Stack
');
        res.writeln('
', e.rhinoException.scriptStackTrace, '
');
        res.writeln('
Java Stack
');
        var writer = new java.io.StringWriter();
        var printer = new java.io.PrintWriter(writer);
        e.rhinoException.printStackTrace(printer);
        res.writeln('
', writer.toString(), '
');
        log.error(e.toString(), e.rhinoException);
    } else {
        log.error(e.toString());
    }
    return res.close();
}

/**
 * Standard notfound page
 */
function notfound(req) {
    var res = new Response();
    res.status = 404;
    res.contentType = 'text/html';
    res.writeln('
Not Found
');
    res.writeln('The requested URL', req.pathDecoded, 'was not found on the server.');
    return res.close();
}

/**
 * Try to load the configuration module.
 * @param configModuleName optional module name, default is 'config'
 */
function getConfig(configModuleName) {
    configModuleName = configModuleName || 'config';
    var config;
    try {
        config = require(configModuleName);
    } catch (noConfig) {
        log.info('Couldn\'t load config module: ' + noConfig);
    }
    return config || {};
}

/**
 * Start the jetty server.
 */
function start(config) {
    // start jetty http server
    config = config || getConfig();
    var httpConfig = config.httpConfig;
    server.start(httpConfig);
}

/**
 * Stop the jetty server.
 */
function stop() {
    // stop jetty HTTP server
    server.stop();
}

if (__name__ == '__main__') {
    for (var i = 1; i < system.args.length; i++) {
        var arg = system.args[i];
        if (arg.indexOf('-') == 0) {
            break;
        }
        system.addRepository(arg);
    }
    log.info('Setup module search: ' + system.getRepositories());
    start();
}
<|endoftext|>


<commit_before>import * as d3 from 'd3';

import { t } from '../util/locale';
import { d3keybinding } from '../lib/d3.keybinding.js';
import { dataShortcuts } from '../../data';
import { svgIcon } from '../svg';
import { uiCmd } from './cmd';
import { uiModal } from './modal';

export function uiShortcuts() {
    var activeTab = 0;
    var modalSelection;
    var savedSelection;

    var keybinding = d3keybinding('shortcuts')
        .on(['?', '⇧/'], function () {
            if (modalSelection) {
                modalSelection.close();
                modalSelection = null;
                return;
            }
            modalSelection = uiModal(savedSelection);
            shortcutsModal(modalSelection);
        });

    d3.select(document)
        .call(keybinding);

    function shortcutsModal(modalSelection) {
        modalSelection.select('.modal')
        .attr('class', 'modal modal-shortcuts fillL col6');

        var shortcutsModal = modalSelection.select('.content');

        shortcutsModal
            .append('div')
            .attr('class', 'modal-section')
            .append('h3')
            .text(t('shortcuts.title'));

        shortcutsModal
            .call(render);
    }

    function render(selection) {
        var wrapper = selection
            .selectAll('.wrapper')
            .data([0]);

        var wrapperEnter = wrapper
            .enter()
            .append('div')
            .attr('class', 'wrapper modal-section');

        var tabsBar = wrapperEnter
            .append('div')
            .attr('class', 'tabs-bar');

        var shortcutsList = wrapperEnter
            .append('div')
            .attr('class', 'shortcuts-list');

        wrapper = wrapper.merge(wrapperEnter);

        var tabs = tabsBar
            .selectAll('.tab')
            .data(dataShortcuts);

        var tabsEnter = tabs
            .enter()
            .append('div')
            .attr('class', 'tab')
            .on('click', function (d, i) {
                activeTab = i;
                render(selection);
            });

        tabsEnter
            .append('span')
            .text(function (d) { return t(d.text); });

        tabs = tabs
            .merge(tabsEnter);

        // Update
        wrapper.selectAll('.tab')
            .classed('active', function (d, i) {
                return i === activeTab;
            });

        var shortcuts = shortcutsList
            .selectAll('.shortcut-tab')
            .data(dataShortcuts);

        var shortcutsEnter = shortcuts
            .enter()
            .append('div')
            .attr('class', 'shortcut-tab');

        var columnsEnter = shortcutsEnter
            .selectAll('.shortcut-column')
            .data(function (d) { return d.columns; })
            .enter()
            .append('table')
            .attr('class', 'shortcut-column');

        var rowsEnter = columnsEnter
            .selectAll('.shortcut-row')
            .data(function (d) { return d.rows; })
            .enter()
            .append('tr')
            .attr('class', 'shortcut-row');

        var sectionRows = rowsEnter
            .filter(function (d) { return !d.shortcuts; });

        sectionRows
            .append('td');

        sectionRows
            .append('td')
            .attr('class', 'shortcut-section')
            .append('h3')
            .text(function (d) { return t(d.text); });

        var shortcutRows = rowsEnter
            .filter(function (d) { return d.shortcuts; });

        var shortcutKeys = shortcutRows
            .append('td')
            .attr('class', 'shortcut-keys');

        var modifierKeys = shortcutKeys
            .filter(function (d) { return d.modifiers; });

        modifierKeys
            .selectAll('kbd.modifier')
            .data(function (d) { return d.modifiers; })
            .enter()
            .each(function () {
                var selection = d3.select(this);

                selection
                    .append('kbd')
                    .attr('class', 'modifier')
                    .text(function (d) { return uiCmd.display(d); });

                selection
                    .append('span')
                    .text('+');
            });

        shortcutKeys
            .selectAll('kbd.shortcut')
            .data(function (d) {
                return d.shortcuts.map(function(s) {
                    return {
                        shortcut: s,
                        separator: d.separator
                    };
                });
            })
            .enter()
            .each(function (d, i, nodes) {
                var selection = d3.select(this);
                var click = d.shortcut.toLowerCase().match(/(.*).click/);

                if (click && click[1]) {
                    selection
                        .call(svgIcon('#walkthrough-mouse', 'mouseclick', click[1]));
                } else {
                    selection
                        .append('kbd')
                        .attr('class', 'shortcut')
                        .text(function (d) {
                            var key = d.shortcut;
                            return key.indexOf('.') !== -1 ? uiCmd.display(t(key)) : uiCmd.display(key);
                        });
                }

                if (i < nodes.length - 1) {
                    selection
                        .append('span')
                        .text(d.separator || '\u00a0' + t('shortcuts.or') + '\u00a0');
                }
            });

        shortcutKeys
            .filter(function(d) { return d.gesture; })
            .each(function () {
                var selection = d3.select(this);

                selection
                    .append('span')
                    .text('+');

                selection
                    .append('span')
                    .attr('class', 'gesture')
                    .text(function (d) { return t(d.gesture); });
            });

        shortcutRows
            .append('td')
            .attr('class', 'shortcut-desc')
            .text(function (d) { return d.text ? t(d.text) : '\u00a0'; });

        shortcuts = shortcuts
            .merge(shortcutsEnter);

        // Update
        wrapper.selectAll('.shortcut-tab')
            .style('display', function (d, i) {
                return i === activeTab ? 'flex' : 'none';
            });
    }

    return function(selection, show) {
        savedSelection = selection;
        if (show) {
            modalSelection = uiModal(selection);
            shortcutsModal(modalSelection);
        }
    };
}
<commit_msg>Fix display of windows redo shortcut<commit_after>import * as d3 from 'd3';

import { t } from '../util/locale';
import { d3keybinding } from '../lib/d3.keybinding.js';
import { dataShortcuts } from '../../data';
import { svgIcon } from '../svg';
import { uiCmd } from './cmd';
import { uiModal } from './modal';
import { utilDetect } from '../util/detect';

export function uiShortcuts() {
    var detected = utilDetect();
    var activeTab = 0;
    var modalSelection;
    var savedSelection;

    var keybinding = d3keybinding('shortcuts')
        .on(['?', '⇧/'], function () {
            if (modalSelection) {
                modalSelection.close();
                modalSelection = null;
                return;
            }
            modalSelection = uiModal(savedSelection);
            shortcutsModal(modalSelection);
        });

    d3.select(document)
        .call(keybinding);

    function shortcutsModal(modalSelection) {
        modalSelection.select('.modal')
        .attr('class', 'modal modal-shortcuts fillL col6');

        var shortcutsModal = modalSelection.select('.content');

        shortcutsModal
            .append('div')
            .attr('class', 'modal-section')
            .append('h3')
            .text(t('shortcuts.title'));

        shortcutsModal
            .call(render);
    }

    function render(selection) {
        var wrapper = selection
            .selectAll('.wrapper')
            .data([0]);

        var wrapperEnter = wrapper
            .enter()
            .append('div')
            .attr('class', 'wrapper modal-section');

        var tabsBar = wrapperEnter
            .append('div')
            .attr('class', 'tabs-bar');

        var shortcutsList = wrapperEnter
            .append('div')
            .attr('class', 'shortcuts-list');

        wrapper = wrapper.merge(wrapperEnter);

        var tabs = tabsBar
            .selectAll('.tab')
            .data(dataShortcuts);

        var tabsEnter = tabs
            .enter()
            .append('div')
            .attr('class', 'tab')
            .on('click', function (d, i) {
                activeTab = i;
                render(selection);
            });

        tabsEnter
            .append('span')
            .text(function (d) { return t(d.text); });

        tabs = tabs
            .merge(tabsEnter);

        // Update
        wrapper.selectAll('.tab')
            .classed('active', function (d, i) {
                return i === activeTab;
            });

        var shortcuts = shortcutsList
            .selectAll('.shortcut-tab')
            .data(dataShortcuts);

        var shortcutsEnter = shortcuts
            .enter()
            .append('div')
            .attr('class', 'shortcut-tab');

        var columnsEnter = shortcutsEnter
            .selectAll('.shortcut-column')
            .data(function (d) { return d.columns; })
            .enter()
            .append('table')
            .attr('class', 'shortcut-column');

        var rowsEnter = columnsEnter
            .selectAll('.shortcut-row')
            .data(function (d) { return d.rows; })
            .enter()
            .append('tr')
            .attr('class', 'shortcut-row');

        var sectionRows = rowsEnter
            .filter(function (d) { return !d.shortcuts; });

        sectionRows
            .append('td');

        sectionRows
            .append('td')
            .attr('class', 'shortcut-section')
            .append('h3')
            .text(function (d) { return t(d.text); });

        var shortcutRows = rowsEnter
            .filter(function (d) { return d.shortcuts; });

        var shortcutKeys = shortcutRows
            .append('td')
            .attr('class', 'shortcut-keys');

        var modifierKeys = shortcutKeys
            .filter(function (d) { return d.modifiers; });

        modifierKeys
            .selectAll('kbd.modifier')
            .data(function (d) {
                if (detected.os === 'win' && d.text === 'shortcuts.editing.commands.redo') {
                    return ['⌘'];
                } else {
                    return d.modifiers;
                }
            })
            .enter()
            .each(function () {
                var selection = d3.select(this);

                selection
                    .append('kbd')
                    .attr('class', 'modifier')
                    .text(function (d) { return uiCmd.display(d); });

                selection
                    .append('span')
                    .text('+');
            });

        shortcutKeys
            .selectAll('kbd.shortcut')
            .data(function (d) {
                var arr;
                if (detected.os === 'win' && d.text === 'shortcuts.editing.commands.redo') {
                    return [{
                        shortcut: 'Y',
                        separator: d.separator
                    }];
                }

                return d.shortcuts.map(function(s) {
                    return {
                        shortcut: s,
                        separator: d.separator
                    };
                });
            })
            .enter()
            .each(function (d, i, nodes) {
                var selection = d3.select(this);
                var click = d.shortcut.toLowerCase().match(/(.*).click/);

                if (click && click[1]) {
                    selection
                        .call(svgIcon('#walkthrough-mouse', 'mouseclick', click[1]));
                } else {
                    selection
                        .append('kbd')
                        .attr('class', 'shortcut')
                        .text(function (d) {
                            var key = d.shortcut;
                            return key.indexOf('.') !== -1 ? uiCmd.display(t(key)) : uiCmd.display(key);
                        });
                }

                if (i < nodes.length - 1) {
                    selection
                        .append('span')
                        .text(d.separator || '\u00a0' + t('shortcuts.or') + '\u00a0');
                }
            });

        shortcutKeys
            .filter(function(d) { return d.gesture; })
            .each(function () {
                var selection = d3.select(this);

                selection
                    .append('span')
                    .text('+');

                selection
                    .append('span')
                    .attr('class', 'gesture')
                    .text(function (d) { return t(d.gesture); });
            });

        shortcutRows
            .append('td')
            .attr('class', 'shortcut-desc')
            .text(function (d) { return d.text ? t(d.text) : '\u00a0'; });

        shortcuts = shortcuts
            .merge(shortcutsEnter);

        // Update
        wrapper.selectAll('.shortcut-tab')
            .style('display', function (d, i) {
                return i === activeTab ? 'flex' : 'none';
            });
    }

    return function(selection, show) {
        savedSelection = selection;
        if (show) {
            modalSelection = uiModal(selection);
            shortcutsModal(modalSelection);
        }
    };
}
<|endoftext|>