<commit_before>var NOTSET = {};

ReactiveObj = function (initialValue, options) {
  var self = this;
  self._obj = typeof initialValue === 'object' ? initialValue : {};
  self._deps = {children: {}, deps: {}};
  self._willInvalidate = [];
  self._willCleanDeps = [];

  if (!options) return;
  if (typeof options.transform === 'function')
    self._transform = options.transform;
};
_.extend(ReactiveObj.prototype, {

  // Returns a normalized keyPath or throws an error if it is invalid
  _matchKeyPath: function (keyPath) {
    if (typeof keyPath === 'string') keyPath = [keyPath];
    else if (typeof keyPath === 'undefined') keyPath = [];
    else if (!(keyPath instanceof Array)) throw new Error('Invalid keypath');

    return keyPath;
  },

  // Visits a path in object and calls the given `visitor` callback
  // Returns NOTSET if path does not exist
  _visitPath: function (node, keyPath, visitor) {
    for (var i=0, l=keyPath.length, s=node, lastS=null; i<l; i+=1) {
      if (typeof s === 'object' && keyPath[i] in s) {
        lastS = s;
        s = s[keyPath[i]];
      }
      else break;
    }
    if (i === l || !keyPath.length)
      return typeof visitor !== 'function' ? s : visitor({
        node: lastS,
        value: s,
        key: keyPath[i - 1]
      });

    return NOTSET;
  },

  // Shallow clone an object with new value at the path
  _copyOnWrite: function (node, path, value) {
    var prevNode = node;
    var newNode = {};
    var currentNode = newNode;
    var currentKey, currentValue;

    for (var i=0, l=path.length-1; i<=l; i+=1) {
      currentKey = path[i];
      if (i === l) currentValue = value;
      else {
        if (prevNode[currentKey] instanceof Array) currentValue = [];
        else currentValue = {};
      }

      if (prevNode) _.extend(currentNode, _.omit(prevNode, currentKey));

      currentNode[currentKey] = currentValue;
      currentNode = currentNode[currentKey];
      if (prevNode) prevNode = prevNode[currentKey];
    }

    return newNode;
  },

  // Add a dependency
  _addDep: function (opt) {
    var self = this;
    var currentNode = self._deps;
    var nextNode;

    if (!opt.path.length) self._addDepToNode(self._deps, opt);

    for (var i=0, l=opt.path.length-1; i<=l; i+=1) {
      nextNode = currentNode.children[opt.path[i]] =
        currentNode.children[opt.path[i]] ||
        {children: {}, deps: {}};

      if (i === l) self._addDepToNode(nextNode, opt);

      currentNode = nextNode;
    }
  },

  _addDepToNode: function (node, opt) {
    node.deps[opt.id] = {
      comp: opt.comp,
      lastVal: opt.lastVal
    };
    if ('equals' in opt) node.deps[opt.id].equals = opt.equals;
  },

  // Remove a dependency
  _removeDep: function (opt) {
    var self = this;
    var currentNode = self._deps;
    var nextNode;

    if (!opt.path.length) self._removeDepFromNode(self._deps, opt);

    for (var i=0, l=opt.path.length-1; i<=l; i+=1) {
      if (!currentNode) break;
      nextNode = currentNode.children[opt.path[i]];

      if (i === l) self._removeDepFromNode(nextNode, opt);

      currentNode = nextNode;
    }
  },

  _removeDepFromNode: function (node, opt) {
    var self = this;
    if (!node.deps[opt.id]) return;

    delete node.deps[opt.id];

    if (self._willCleanDeps.length === 0) {
      Tracker.afterFlush(function () {
        self._cleanDeps();
      });
    }
    self._willCleanDeps.push(opt.path);
  },

  // Checks an array of paths and remove empty nodes depth first
  _cleanDeps: function () {
    var self = this;
    var cleanPaths = _.chain(self._willCleanDeps)
    .uniq(function (v) {
      return JSON.stringify(v);
    })
    .sortBy(function (v) { return -1 * v.length; })
    .each(function (path) {
      var currentNode = self._deps;
      var nextNode;

      for (var i=0, l=path.length-1; i<=l; i+=1) {
        if (!currentNode || !currentNode.children) return;

        nextNode = currentNode.children[path[i]];
        if(i === l) {
          if (!_.size(nextNode.children) && !_.size(nextNode.deps))
            delete currentNode.children[path[i]];
        }
        currentNode = nextNode;
      }
    })
    .value();

    // Reset
    self._willCleanDeps = [];
  },

  // Internal get with more options
  _get: function (keyPath, options) {
    var self = this;
    var computation, id, value, depOptions;
    keyPath = self._matchKeyPath(keyPath);
    options = options || {};

    value = self._visitPath(self._obj, keyPath);

    if (Tracker.active) {
      computation = Tracker.currentComputation;
      id = computation._id;
      depOptions = {
        id: id,
        path: keyPath,
        comp: computation,
        lastVal: value
      };
      if ('equals' in options) depOptions.equals = options.equals;
      self._addDep(depOptions);
      Tracker.currentComputation.onInvalidate(function () {
        self._removeDep({path: keyPath, id: id});
      });
    }

    if (value === NOTSET) return options.valIfNotSet;
    if (options.noTransform) return value;
    else return self._transform ? self._transform(value) : value;
  },

  get: function (keyPath, valIfNotSet) {
    return this._get(keyPath, {valIfNotSet: valIfNotSet});
  },

  equals: function (keyPath, value) {
    return value === this._get(keyPath, {
      equals: value,
      valIfNotSet: undefined,
      noTransform: true
    });
  },

  set: function (keyPath, value) {
    var self = this;
    var newState, noop;
    if (arguments.length < 2) throw new Error("No value to set");
    keyPath = self._matchKeyPath(keyPath);

    // Replace root node
    if (!keyPath.length) newState = value;

    // If value assigned is the same as the old value, it is a noop
    noop = self._visitPath(self._obj, keyPath, function (context) {
      if (context.value === value) return true;
    });
    if (noop === true) return self;

    // Replace state
    self._obj = newState || self._copyOnWrite(self._obj, keyPath, value);

    self.invalidate(keyPath);

    return self;
  },

  update: function (keyPath, valIfNotSet, updater) {
    var self = this;
    var useNotSetVal = true;
    var write = {};
    var oldVal, newVal;
    keyPath = self._matchKeyPath(keyPath);
    if (arguments.length < 2) throw new Error('Insufficient arguments');
    else if (arguments.length === 2) {
      updater = valIfNotSet;
      useNotSetVal = false;
    }
    if (typeof updater !== 'function')
      throw new Error('Invalid or missing updater function');

    oldVal = self._visitPath(self._obj, keyPath);
    if (oldVal === NOTSET) {
      if (useNotSetVal) write.value = valIfNotSet;
    }
    else {
      newVal = updater(self._transform ? self._transform(oldVal) : oldVal);
      if (oldVal !== newVal) write.value = newVal;
    }

    if ('value' in write) {
      self._obj = self._copyOnWrite(self._obj, keyPath, write.value);
      self.invalidate(keyPath);
    }

    return self;
  },

  setDefault: function (keyPath, valIfNotSet) {
    var self = this;
    var value = Tracker.nonreactive(function () {
      return self.get(keyPath, NOTSET);
    });
    if (value === NOTSET) self.set(keyPath, valIfNotSet);

    return self;
  },

  // Breadth first traversal
  _traverse: function (tree, callback) {
    var queue = [];
    var next = {parent: null, node: tree, path: []};
    var nextNodes;

    while (next) {
      nextNodes = callback(next);
      if (typeof nextNodes === 'object') {
        _.each(nextNodes, function(node, k) {
          if (!node) return;
          queue.push({parent: next, node: node, path: next.path.concat(k)});
        });
      }
      next = queue.shift();
    }
  },

  // Clear the cached values of deps in a given node
  _resetDeps: function (deps, allTypes) {
    var depKeys = _.keys(deps);

    for (var i=0, l=depKeys.length, d; i<l; i+=1) {
      d = deps[depKeys[i]];
      if (allTypes) delete d.lastVal;
      else if (d.lastVal instanceof Object) delete d.lastVal;
    };
  },

  forceInvalidate: function (keyPath, options) {
    var self = this;
    var path, lastNode;
    keyPath = self._matchKeyPath(keyPath);
    path = self._keyPathToDepPath(keyPath);
    options = options || {};
    _.defaults(options, {
      allType: false,
      noChildren: false
    });

    self._visitPath(self._deps, path, function (context) {
      self._resetDeps(context.node.deps, options.allTypes);
      lastNode = context.node;
    });

    if (!options.noChildren && lastNode)
      self._traverse(lastNode, function (nodeDes) {
        self._resetDeps(nodeDes.node.deps, options.allTypes);
        return nodeDes.node.children;
      });

    self.invalidate(keyPath);
  },

  // Create a tree from an array of paths
  _makePathTree: function (pathArr) {
    return _.chain(pathArr)
    .sortBy(function (keyPath) { return keyPath.length; })
    .reduce(function (tree, keyPath) {
      var currentPath = tree;
      if (tree === true) return tree;

      if (keyPath.length === 0) return tree = true;

      for (var i=0, l=keyPath.length - 1, v; i<=l; i+=1) {
        v = currentPath[keyPath[i]];

        if (v === true) return tree;

        if (i === l) currentPath[keyPath[i]] = true;
        else if (!v) currentPath[keyPath[i]] = {};

        currentPath = currentPath[keyPath[i]];
      }

      return tree;
    }, {})
    .value();
  },

  // Convert a value keypath to a path in the deps tree
  _keyPathToDepPath: function (keyPath) {
    var path = ['children'];
    for (var i=0, l=keyPath.length; i<l; i+=1) {
      path.push(keyPath[i], 'children');
    }
    return path;
  },

  // Process outstanding invalidations in the queue
  _processInvalidations: function () {
    var self = this;
    var invalidTree;
    if (self._willInvalidate.length === 0) return;

    // Populate invalidation tree
    invalidTree = self._makePathTree(self._willInvalidate);

    self._invalidateTree(invalidTree);

    // Reset
    self._willInvalidate = [];
  },

  // Invalidate deps in a given tree
  _invalidateTree: function (invalidTree) {
    var self = this;
    self._traverse(invalidTree, function (nodeDes) {
      self._invalidatePath(
        nodeDes.path,
        nodeDes.node === true ? true : undefined
      );
      return nodeDes.node;
    });
  },

  // Invalidate deps in a given path
  _invalidatePath: function (keyPath, invalidateChildren) {
    var self = this;
    var path = self._keyPathToDepPath(keyPath);
    var lastNode;

    self._visitPath(self._deps, path, function (context) {
      self._invalidateDeps(keyPath, context.node.deps);
      lastNode = context.node;
    });

    if (lastNode && invalidateChildren)
      self._traverse(lastNode, function (nodeDes) {
        if (nodeDes.path.length) {
          var path = keyPath.concat(nodeDes.path);
          self._invalidateDeps(path, nodeDes.node.deps);
        }
        return nodeDes.node.children;
      });
  },

  // Invalidate deps in a given node
  // keyPath is used to find the current value
  _invalidateDeps: function (keyPath, deps) {
    var self = this;
    var depKeys = _.keys(deps);
    var val = self._visitPath(self._obj, keyPath);
    if (val === NOTSET) val = undefined;

    for (var i=0, l=depKeys.length, d; i<l; i+=1) {
      d = deps[depKeys[i]];
      if (
        !('lastVal' in d) ||
        (
          ('equals' in d) &&
          ((d.lastVal === d.equals ?1:0) + (val === d.equals ?1:0) === 1)
        ) ||
        val !== d.lastVal
      ) d.comp.invalidate();
    };
  },

  // Triggers invalidation
  invalidate: function (keyPath) {
    var self = this;

    // Calls invalidate later if not already
    if (self._willInvalidate.length === 0) {
      Tracker.afterFlush(function () {
        self._processInvalidations();
      });
    }

    if (typeof keyPath === 'string') keyPath = [keyPath];
    else if (!(keyPath instanceof Array)) keyPath = [];
    self._willInvalidate.push(keyPath);
  }
});
<commit_msg>Parse keypath strings with dot notation<commit_after>var NOTSET = {};

ReactiveObj = function (initialValue, options) {
  var self = this;
  self._obj = typeof initialValue === 'object' ? initialValue : {};
  self._deps = {children: {}, deps: {}};
  self._willInvalidate = [];
  self._willCleanDeps = [];

  if (!options) return;
  if (typeof options.transform === 'function')
    self._transform = options.transform;
};
_.extend(ReactiveObj.prototype, {

  // Returns a normalized keyPath or throws an error if it is invalid
  _matchKeyPath: function (keyPath) {
    if (typeof keyPath === 'string') keyPath = keyPath.split('.');
    else if (typeof keyPath === 'undefined') keyPath = [];
    else if (!(keyPath instanceof Array)) throw new Error('Invalid keypath');

    return keyPath;
  },

  // Visits a path in object and calls the given `visitor` callback
  // Returns NOTSET if path does not exist
  _visitPath: function (node, keyPath, visitor) {
    for (var i=0, l=keyPath.length, s=node, lastS=null; i<l; i+=1) {
      if (typeof s === 'object' && keyPath[i] in s) {
        lastS = s;
        s = s[keyPath[i]];
      }
      else break;
    }
    if (i === l || !keyPath.length)
      return typeof visitor !== 'function' ? s : visitor({
        node: lastS,
        value: s,
        key: keyPath[i - 1]
      });

    return NOTSET;
  },

  // Shallow clone an object with new value at the path
  _copyOnWrite: function (node, path, value) {
    var prevNode = node;
    var newNode = {};
    var currentNode = newNode;
    var currentKey, currentValue;

    for (var i=0, l=path.length-1; i<=l; i+=1) {
      currentKey = path[i];
      if (i === l) currentValue = value;
      else {
        if (prevNode[currentKey] instanceof Array) currentValue = [];
        else currentValue = {};
      }

      if (prevNode) _.extend(currentNode, _.omit(prevNode, currentKey));

      currentNode[currentKey] = currentValue;
      currentNode = currentNode[currentKey];
      if (prevNode) prevNode = prevNode[currentKey];
    }

    return newNode;
  },

  // Add a dependency
  _addDep: function (opt) {
    var self = this;
    var currentNode = self._deps;
    var nextNode;

    if (!opt.path.length) self._addDepToNode(self._deps, opt);

    for (var i=0, l=opt.path.length-1; i<=l; i+=1) {
      nextNode = currentNode.children[opt.path[i]] =
        currentNode.children[opt.path[i]] ||
        {children: {}, deps: {}};

      if (i === l) self._addDepToNode(nextNode, opt);

      currentNode = nextNode;
    }
  },

  _addDepToNode: function (node, opt) {
    node.deps[opt.id] = {
      comp: opt.comp,
      lastVal: opt.lastVal
    };
    if ('equals' in opt) node.deps[opt.id].equals = opt.equals;
  },

  // Remove a dependency
  _removeDep: function (opt) {
    var self = this;
    var currentNode = self._deps;
    var nextNode;

    if (!opt.path.length) self._removeDepFromNode(self._deps, opt);

    for (var i=0, l=opt.path.length-1; i<=l; i+=1) {
      if (!currentNode) break;
      nextNode = currentNode.children[opt.path[i]];

      if (i === l) self._removeDepFromNode(nextNode, opt);

      currentNode = nextNode;
    }
  },

  _removeDepFromNode: function (node, opt) {
    var self = this;
    if (!node.deps[opt.id]) return;

    delete node.deps[opt.id];

    if (self._willCleanDeps.length === 0) {
      Tracker.afterFlush(function () {
        self._cleanDeps();
      });
    }
    self._willCleanDeps.push(opt.path);
  },

  // Checks an array of paths and remove empty nodes depth first
  _cleanDeps: function () {
    var self = this;
    var cleanPaths = _.chain(self._willCleanDeps)
    .uniq(function (v) {
      return JSON.stringify(v);
    })
    .sortBy(function (v) { return -1 * v.length; })
    .each(function (path) {
      var currentNode = self._deps;
      var nextNode;

      for (var i=0, l=path.length-1; i<=l; i+=1) {
        if (!currentNode || !currentNode.children) return;

        nextNode = currentNode.children[path[i]];
        if(i === l) {
          if (!_.size(nextNode.children) && !_.size(nextNode.deps))
            delete currentNode.children[path[i]];
        }
        currentNode = nextNode;
      }
    })
    .value();

    // Reset
    self._willCleanDeps = [];
  },

  // Internal get with more options
  _get: function (keyPath, options) {
    var self = this;
    var computation, id, value, depOptions;
    keyPath = self._matchKeyPath(keyPath);
    options = options || {};

    value = self._visitPath(self._obj, keyPath);

    if (Tracker.active) {
      computation = Tracker.currentComputation;
      id = computation._id;
      depOptions = {
        id: id,
        path: keyPath,
        comp: computation,
        lastVal: value
      };
      if ('equals' in options) depOptions.equals = options.equals;
      self._addDep(depOptions);
      Tracker.currentComputation.onInvalidate(function () {
        self._removeDep({path: keyPath, id: id});
      });
    }

    if (value === NOTSET) return options.valIfNotSet;
    if (options.noTransform) return value;
    else return self._transform ? self._transform(value) : value;
  },

  get: function (keyPath, valIfNotSet) {
    return this._get(keyPath, {valIfNotSet: valIfNotSet});
  },

  equals: function (keyPath, value) {
    return value === this._get(keyPath, {
      equals: value,
      valIfNotSet: undefined,
      noTransform: true
    });
  },

  set: function (keyPath, value) {
    var self = this;
    var newState, noop;
    if (arguments.length < 2) throw new Error("No value to set");
    keyPath = self._matchKeyPath(keyPath);

    // Replace root node
    if (!keyPath.length) newState = value;

    // If value assigned is the same as the old value, it is a noop
    noop = self._visitPath(self._obj, keyPath, function (context) {
      if (context.value === value) return true;
    });
    if (noop === true) return self;

    // Replace state
    self._obj = newState || self._copyOnWrite(self._obj, keyPath, value);

    self.invalidate(keyPath);

    return self;
  },

  update: function (keyPath, valIfNotSet, updater) {
    var self = this;
    var useNotSetVal = true;
    var write = {};
    var oldVal, newVal;
    keyPath = self._matchKeyPath(keyPath);
    if (arguments.length < 2) throw new Error('Insufficient arguments');
    else if (arguments.length === 2) {
      updater = valIfNotSet;
      useNotSetVal = false;
    }
    if (typeof updater !== 'function')
      throw new Error('Invalid or missing updater function');

    oldVal = self._visitPath(self._obj, keyPath);
    if (oldVal === NOTSET) {
      if (useNotSetVal) write.value = valIfNotSet;
    }
    else {
      newVal = updater(self._transform ? self._transform(oldVal) : oldVal);
      if (oldVal !== newVal) write.value = newVal;
    }

    if ('value' in write) {
      self._obj = self._copyOnWrite(self._obj, keyPath, write.value);
      self.invalidate(keyPath);
    }

    return self;
  },

  setDefault: function (keyPath, valIfNotSet) {
    var self = this;
    var value = Tracker.nonreactive(function () {
      return self.get(keyPath, NOTSET);
    });
    if (value === NOTSET) self.set(keyPath, valIfNotSet);

    return self;
  },

  // Breadth first traversal
  _traverse: function (tree, callback) {
    var queue = [];
    var next = {parent: null, node: tree, path: []};
    var nextNodes;

    while (next) {
      nextNodes = callback(next);
      if (typeof nextNodes === 'object') {
        _.each(nextNodes, function(node, k) {
          if (!node) return;
          queue.push({parent: next, node: node, path: next.path.concat(k)});
        });
      }
      next = queue.shift();
    }
  },

  // Clear the cached values of deps in a given node
  _resetDeps: function (deps, allTypes) {
    var depKeys = _.keys(deps);

    for (var i=0, l=depKeys.length, d; i<l; i+=1) {
      d = deps[depKeys[i]];
      if (allTypes) delete d.lastVal;
      else if (d.lastVal instanceof Object) delete d.lastVal;
    };
  },

  forceInvalidate: function (keyPath, options) {
    var self = this;
    var path, lastNode;
    keyPath = self._matchKeyPath(keyPath);
    path = self._keyPathToDepPath(keyPath);
    options = options || {};
    _.defaults(options, {
      allType: false,
      noChildren: false
    });

    self._visitPath(self._deps, path, function (context) {
      self._resetDeps(context.node.deps, options.allTypes);
      lastNode = context.node;
    });

    if (!options.noChildren && lastNode)
      self._traverse(lastNode, function (nodeDes) {
        self._resetDeps(nodeDes.node.deps, options.allTypes);
        return nodeDes.node.children;
      });

    self.invalidate(keyPath);
  },

  // Create a tree from an array of paths
  _makePathTree: function (pathArr) {
    return _.chain(pathArr)
    .sortBy(function (keyPath) { return keyPath.length; })
    .reduce(function (tree, keyPath) {
      var currentPath = tree;
      if (tree === true) return tree;

      if (keyPath.length === 0) return tree = true;

      for (var i=0, l=keyPath.length - 1, v; i<=l; i+=1) {
        v = currentPath[keyPath[i]];

        if (v === true) return tree;

        if (i === l) currentPath[keyPath[i]] = true;
        else if (!v) currentPath[keyPath[i]] = {};

        currentPath = currentPath[keyPath[i]];
      }

      return tree;
    }, {})
    .value();
  },

  // Convert a value keypath to a path in the deps tree
  _keyPathToDepPath: function (keyPath) {
    var path = ['children'];
    for (var i=0, l=keyPath.length; i<l; i+=1) {
      path.push(keyPath[i], 'children');
    }
    return path;
  },

  // Process outstanding invalidations in the queue
  _processInvalidations: function () {
    var self = this;
    var invalidTree;
    if (self._willInvalidate.length === 0) return;

    // Populate invalidation tree
    invalidTree = self._makePathTree(self._willInvalidate);

    self._invalidateTree(invalidTree);

    // Reset
    self._willInvalidate = [];
  },

  // Invalidate deps in a given tree
  _invalidateTree: function (invalidTree) {
    var self = this;
    self._traverse(invalidTree, function (nodeDes) {
      self._invalidatePath(
        nodeDes.path,
        nodeDes.node === true ? true : undefined
      );
      return nodeDes.node;
    });
  },

  // Invalidate deps in a given path
  _invalidatePath: function (keyPath, invalidateChildren) {
    var self = this;
    var path = self._keyPathToDepPath(keyPath);
    var lastNode;

    self._visitPath(self._deps, path, function (context) {
      self._invalidateDeps(keyPath, context.node.deps);
      lastNode = context.node;
    });

    if (lastNode && invalidateChildren)
      self._traverse(lastNode, function (nodeDes) {
        if (nodeDes.path.length) {
          var path = keyPath.concat(nodeDes.path);
          self._invalidateDeps(path, nodeDes.node.deps);
        }
        return nodeDes.node.children;
      });
  },

  // Invalidate deps in a given node
  // keyPath is used to find the current value
  _invalidateDeps: function (keyPath, deps) {
    var self = this;
    var depKeys = _.keys(deps);
    var val = self._visitPath(self._obj, keyPath);
    if (val === NOTSET) val = undefined;

    for (var i=0, l=depKeys.length, d; i<l; i+=1) {
      d = deps[depKeys[i]];
      if (
        !('lastVal' in d) ||
        (
          ('equals' in d) &&
          ((d.lastVal === d.equals ?1:0) + (val === d.equals ?1:0) === 1)
        ) ||
        val !== d.lastVal
      ) d.comp.invalidate();
    };
  },

  // Triggers invalidation
  invalidate: function (keyPath) {
    var self = this;

    // Calls invalidate later if not already
    if (self._willInvalidate.length === 0) {
      Tracker.afterFlush(function () {
        self._processInvalidations();
      });
    }

    if (typeof keyPath === 'string') keyPath = [keyPath];
    else if (!(keyPath instanceof Array)) keyPath = [];
    self._willInvalidate.push(keyPath);
  }
});
<|endoftext|>


<commit_before>//= require "./post_view"

app.views.SmallFrame = app.views.Post.extend({

  SINGLE_COLUMN_WIDTH : 265,
  DOUBLE_COLUMN_WIDTH : 560,

  className : "canvas-frame",

  templateName : "small-frame",

  events : {
    "click .content" : "favoritePost",
    "click .delete" : "killPost",
    "click .info" : "goToPost"
  },

  subviews : {
    '.embed-frame' : "oEmbedView"
  },

  oEmbedView : function(){
    return new app.views.OEmbed({model : this.model})
  },

  presenter : function(){
    //todo : we need to have something better for small frame text, probably using the headline() scenario.
    return _.extend(this.defaultPresenter(),
      {
        text : this.model && app.helpers.textFormatter(this.model.get("text"), this.model),
        adjustedImageHeight : this.adjustedImageHeight(),
        likesCount : this.model.interactions.likesCount(),
        resharesCount : this.model.interactions.resharesCount(),
        commentsCount : this.model.interactions.commentsCount()
      })
  },

  initialize : function() {
    this.$el.addClass([this.dimensionsClass(), this.colorClass(), this.frameClass()].join(' '))
    return this;
  },

  postRenderTemplate : function() {
    this.$el.addClass([this.dimensionsClass(), this.colorClass(), this.frameClass()].join(' '))
  },

  frameClass : function(){
    var name = this.model.get("frame_name") || ""
    return name.toLowerCase()
  },

  colorClass : function() {
    var text = this.model.get("text")
      , baseClass = $.trim(text).length == 0 ? "no-text" : 'has-text';

    if(baseClass == "no-text" || this.model.get("photos").length > 0 || this.model.get("o_embed_cache")) { return baseClass }

    var randomColor = _.first(_.shuffle(['cyan', 'green', 'yellow', 'purple', 'lime-green', 'orange', 'red', 'turquoise', 'sand']));

    var textClass;
    if(text.length > 240) {
      textClass = "blog-text x2 width"
    } else if(text.length > 140) {
      textClass = randomColor
    } else if(text.length > 40) {
      textClass = randomColor
    } else {
      textClass =  "big-text " + randomColor
    }

    return [baseClass, textClass, "sticky-note"].join(" ")
  },

  dimensionsClass : function() {
    return (this.model.get("favorite")) ?  "x2 width height" : ""
  },

  adjustedImageHeight : function() {
    if(!(this.model.get("photos") || [])[0]) { return }

    var modifiers = [this.dimensionsClass(), this.colorClass()].join(' ')
      , width;

    /* mobile width
    *
    *  currently does not re-calculate on orientation change */
    if($(window).width() <= 767) {
      width = $(window).width();
    }

    var firstPhoto = this.model.get("photos")[0]
      , width = width || (modifiers.search("x2") != -1 ? this.DOUBLE_COLUMN_WIDTH : this.SINGLE_COLUMN_WIDTH)
      , ratio = width / firstPhoto.dimensions.width;

    return(ratio * firstPhoto.dimensions.height)
  },

  favoritePost : function(evt) {
    if(evt) {
      /* follow links instead of faving the targeted post */
      if($(evt.target).is('a')) { return }

      evt.stopImmediatePropagation(); evt.preventDefault();
    }

    var prevDimension = this.dimensionsClass();

    this.model.toggleFavorite({save : this.model.get("author").diaspora_id == app.currentUser.get("diaspora_id")})

    this.$el.removeClass(prevDimension)
    this.render()

    app.page.stream.trigger("reLayout")
    //trigger moar relayouts in the case of images WHOA GROSS HAX
    _.delay(function(){app.page.stream.trigger("reLayout")}, 200)
  },

  killPost : function(){
    this.destroyModel()
    _.delay(function(){app.page.stream.trigger("reLayout")}, 0)
  },

  goToPost : function(evt) {
    if(evt) { evt.stopImmediatePropagation(); }
    app.router.navigate(this.model.url(), true)
  }
});<commit_msg>make post load faster from profile to show<commit_after>//= require "./post_view"

app.views.SmallFrame = app.views.Post.extend({

  SINGLE_COLUMN_WIDTH : 265,
  DOUBLE_COLUMN_WIDTH : 560,

  className : "canvas-frame",

  templateName : "small-frame",

  events : {
    "click .content" : "favoritePost",
    "click .delete" : "killPost",
    "click .info" : "goToPost"
  },

  subviews : {
    '.embed-frame' : "oEmbedView"
  },

  oEmbedView : function(){
    return new app.views.OEmbed({model : this.model})
  },

  presenter : function(){
    //todo : we need to have something better for small frame text, probably using the headline() scenario.
    return _.extend(this.defaultPresenter(),
      {
        text : this.model && app.helpers.textFormatter(this.model.get("text"), this.model),
        adjustedImageHeight : this.adjustedImageHeight(),
        likesCount : this.model.interactions.likesCount(),
        resharesCount : this.model.interactions.resharesCount(),
        commentsCount : this.model.interactions.commentsCount()
      })
  },

  initialize : function() {
    this.$el.addClass([this.dimensionsClass(), this.colorClass(), this.frameClass()].join(' '))
    return this;
  },

  postRenderTemplate : function() {
    this.$el.addClass([this.dimensionsClass(), this.colorClass(), this.frameClass()].join(' '))
  },

  frameClass : function(){
    var name = this.model.get("frame_name") || ""
    return name.toLowerCase()
  },

  colorClass : function() {
    var text = this.model.get("text")
      , baseClass = $.trim(text).length == 0 ? "no-text" : 'has-text';

    if(baseClass == "no-text" || this.model.get("photos").length > 0 || this.model.get("o_embed_cache")) { return baseClass }

    var randomColor = _.first(_.shuffle(['cyan', 'green', 'yellow', 'purple', 'lime-green', 'orange', 'red', 'turquoise', 'sand']));

    var textClass;
    if(text.length > 240) {
      textClass = "blog-text x2 width"
    } else if(text.length > 140) {
      textClass = randomColor
    } else if(text.length > 40) {
      textClass = randomColor
    } else {
      textClass =  "big-text " + randomColor
    }

    return [baseClass, textClass, "sticky-note"].join(" ")
  },

  dimensionsClass : function() {
    return (this.model.get("favorite")) ?  "x2 width height" : ""
  },

  adjustedImageHeight : function() {
    if(!(this.model.get("photos") || [])[0]) { return }

    var modifiers = [this.dimensionsClass(), this.colorClass()].join(' ')
      , width;

    /* mobile width
    *
    *  currently does not re-calculate on orientation change */
    if($(window).width() <= 767) {
      width = $(window).width();
    }

    var firstPhoto = this.model.get("photos")[0]
      , width = width || (modifiers.search("x2") != -1 ? this.DOUBLE_COLUMN_WIDTH : this.SINGLE_COLUMN_WIDTH)
      , ratio = width / firstPhoto.dimensions.width;

    return(ratio * firstPhoto.dimensions.height)
  },

  favoritePost : function(evt) {
    if(evt) {
      /* follow links instead of faving the targeted post */
      if($(evt.target).is('a')) { return }

      evt.stopImmediatePropagation(); evt.preventDefault();
    }

    var prevDimension = this.dimensionsClass();

    this.model.toggleFavorite({save : this.model.get("author").diaspora_id == app.currentUser.get("diaspora_id")})

    this.$el.removeClass(prevDimension)
    this.render()

    app.page.stream.trigger("reLayout")
    //trigger moar relayouts in the case of images WHOA GROSS HAX
    _.delay(function(){app.page.stream.trigger("reLayout")}, 200)
  },

  killPost : function(){
    this.destroyModel()
    _.delay(function(){app.page.stream.trigger("reLayout")}, 0)
  },

  goToPost : function(evt) {
    if(evt) { evt.stopImmediatePropagation(); }
    window.preloads.post = this.model.attributes
    app.router.navigate(this.model.url(), true)
  }
});<|endoftext|>


<commit_before>/*
 * Angular application routes.
 * Uses the 'app' variable defined in app.js, so must be loaded after it.
 */
app.config([
  '$stateProvider', '$locationProvider', '$urlRouterProvider', 'ROUTE_UTILS',
  function ($stateProvider, $locationProvider, $urlRouterProvider,
            ROUTE_UTILS) {

    var R = ROUTE_UTILS; // Shortcut

    $locationProvider.html5Mode(true);

    $urlRouterProvider.otherwise('/');

    $stateProvider
      // App routes
      // The 'top level' view corresponding to AppCtrl
      .state('app', {
        abstract: true,
        url: '',
        templateUrl: 'client/controllers/app/index.html',
        controller: 'AppCtrl'
      })

      // Home routes
      .state('app.home', {
        url: '/',
        templateUrl: 'client/controllers/home/index.html',
        controller: 'HomeCtrl'
      })

      // Post routes
      .state('app.posts', {
        abstract: true,
        url: '/posts',
        template: '

',
        resolve: {
          initialData: angular.noop
        }
      })
      .state('app.posts.index', {
        url: '',
        templateUrl: 'client/controllers/posts/index.html',
        controller: 'PostsCtrl'
      })
      .state('app.posts.new', {
        url: '/new',
        templateUrl: 'client/controllers/posts/new.html',
        controller: 'PostsCtrl',
        resolve: {
          auth: R.requireSignIn(),
          initialData: ['Post', function (Post) {
            // It is good practice to initialize to non-null values
            return new Post({ message: '' });
          }]
        }
      })
      .state('app.posts.show', {
        url: '/:id',
        templateUrl: 'client/controllers/posts/show.html',
        controller: 'PostsCtrl',
        resolve: {
          initialData: ['$stateParams', 'Post', function ($stateParams, Post) {
            return Post.get({ postId: $stateParams.id }).$promise;
          }]
        }
      })
      .state('app.posts.edit', {
        url: '/:id/edit',
        templateUrl: 'client/controllers/posts/edit.html',
        controller: 'PostsCtrl',
        resolve: {
          auth: R.requireSignIn(),
          auth2: R.requireServerAuth('/:locale/posts/:id/edit'),
          initialData: ['$stateParams', 'Post', function ($stateParams, Post) {
            return Post.edit({ postId: $stateParams.id }).$promise;
          }]
        }
      })

      // Attachment routes
      .state('app.attachments', {
        abstract: true,
        url: '/attachments',
        template: '

',
        resolve: {
          auth: R.requireSignIn(),
          initialData: angular.noop
        }
      })
      .state('app.attachments.show', {
        url: '/:id',
        templateUrl: 'client/controllers/attachments/show.html',
        controller: 'AttachmentsCtrl',
        resolve: {
          auth2: R.requireServerAuth('/:locale/attachments/:id'),
          initialData: [
            '$stateParams', 'Attachment',
            function ($stateParams, Attachment) {
              return Attachment.get({ attachmentId: $stateParams.id }).$promise;
            }]
        }
      })

      // Error routes
      .state('401', {
        url: '/401',
        templateUrl: 'shared/401.html'
      })
      .state('404', {
        url: '/404',
        templateUrl: 'shared/404.html'
      })
      .state('500', {
        url: '/500',
        templateUrl: 'shared/500.html'
      });
  }]);

app.run([
  '$injector', 'ROUTE_UTILS',
  function ($injector, ROUTE_UTILS) {
    $injector.invoke(ROUTE_UTILS.onAppRun);
  }]);
<commit_msg>fixed calls to R.requireServerAuth<commit_after>/*
 * Angular application routes.
 * Uses the 'app' variable defined in app.js, so must be loaded after it.
 */
app.config([
  '$stateProvider', '$locationProvider', '$urlRouterProvider', 'ROUTE_UTILS',
  function ($stateProvider, $locationProvider, $urlRouterProvider,
            ROUTE_UTILS) {

    var R = ROUTE_UTILS; // Shortcut

    $locationProvider.html5Mode(true);

    $urlRouterProvider.otherwise('/');

    $stateProvider
      // App routes
      // The 'top level' view corresponding to AppCtrl
      .state('app', {
        abstract: true,
        url: '',
        templateUrl: 'client/controllers/app/index.html',
        controller: 'AppCtrl'
      })

      // Home routes
      .state('app.home', {
        url: '/',
        templateUrl: 'client/controllers/home/index.html',
        controller: 'HomeCtrl'
      })

      // Post routes
      .state('app.posts', {
        abstract: true,
        url: '/posts',
        template: '

',
        resolve: {
          initialData: angular.noop
        }
      })
      .state('app.posts.index', {
        url: '',
        templateUrl: 'client/controllers/posts/index.html',
        controller: 'PostsCtrl'
      })
      .state('app.posts.new', {
        url: '/new',
        templateUrl: 'client/controllers/posts/new.html',
        controller: 'PostsCtrl',
        resolve: {
          auth: R.requireSignIn(),
          initialData: ['Post', function (Post) {
            // It is good practice to initialize to non-null values
            return new Post({ message: '' });
          }]
        }
      })
      .state('app.posts.show', {
        url: '/:id',
        templateUrl: 'client/controllers/posts/show.html',
        controller: 'PostsCtrl',
        resolve: {
          initialData: ['$stateParams', 'Post', function ($stateParams, Post) {
            return Post.get({ postId: $stateParams.id }).$promise;
          }]
        }
      })
      .state('app.posts.edit', {
        url: '/:id/edit',
        templateUrl: 'client/controllers/posts/edit.html',
        controller: 'PostsCtrl',
        resolve: {
          auth: R.requireSignIn(),
          auth2: R.requireServerAuth('/:locale/posts/:id/edit.json'),
          initialData: ['$stateParams', 'Post', function ($stateParams, Post) {
            return Post.edit({ postId: $stateParams.id }).$promise;
          }]
        }
      })

      // Attachment routes
      .state('app.attachments', {
        abstract: true,
        url: '/attachments',
        template: '

',
        resolve: {
          auth: R.requireSignIn(),
          initialData: angular.noop
        }
      })
      .state('app.attachments.show', {
        url: '/:id',
        templateUrl: 'client/controllers/attachments/show.html',
        controller: 'AttachmentsCtrl',
        resolve: {
          auth2: R.requireServerAuth('/:locale/attachments/:id.json'),
          initialData: [
            '$stateParams', 'Attachment',
            function ($stateParams, Attachment) {
              return Attachment.get({ attachmentId: $stateParams.id }).$promise;
            }]
        }
      })

      // Error routes
      .state('401', {
        url: '/401',
        templateUrl: 'shared/401.html'
      })
      .state('404', {
        url: '/404',
        templateUrl: 'shared/404.html'
      })
      .state('500', {
        url: '/500',
        templateUrl: 'shared/500.html'
      });
  }]);

app.run([
  '$injector', 'ROUTE_UTILS',
  function ($injector, ROUTE_UTILS) {
    $injector.invoke(ROUTE_UTILS.onAppRun);
  }]);
<|endoftext|>


<commit_before>define(['jquery', 'DoughBaseComponent'], function($, DoughBaseComponent) {

  'use strict';

  var ClearInput,
      uiEvents = {
      'keydown [data-dough-clear-input]' : 'updateResetButton',
      'click [data-dough-clear-input-button]' : 'resetForm'
      };

  ClearInput = function($el, config) {
    this.uiEvents = uiEvents;
    ClearInput.baseConstructor.call(this, $el, config);

    this.$resetInput = this.$el.find('[data-dough-clear-input]');
    this.$resetButton = this.$el.find('[data-dough-clear-input-button]');

    this.updateResetButton();
  };

  /**
  * Inherit from base module, for shared methods and interface
  */
  DoughBaseComponent.extend(ClearInput);

  /**
  * Set up and populate the model from the form inputs
  * @param {Promise} initialised
  */
  ClearInput.prototype.init = function(initialised) {
    this._initialisedSuccess(initialised);
  };

  ClearInput.prototype.updateResetButton = function() {
    var fn = this.$resetInput.val() === '' ? 'removeClass' : 'addClass';
    this.$resetButton[fn]('is-active');
  };

// We are progressively enhancing the form with JS. The CSS button, type 'reset'
// resets the form faster than the JS can run, so we need to invoke reset() to ensure the correct behaviour.

  ClearInput.prototype.resetForm = function(e) {
    this.$resetInput.val('');
    this.updateResetButton();
    this.$resetInput.focus();
    e.preventDefault();
  };

  return ClearInput;

});
<commit_msg>Fixing issue which has crept to LIVE environment which resulted in the clear input script not firing it's events<commit_after>define(['jquery', 'DoughBaseComponent'], function($, DoughBaseComponent) {

  'use strict';

  var ClearInput,
      defaultConfig = {
        uiEvents: {
          'keydown [data-dough-clear-input]' : 'updateResetButton',
          'click [data-dough-clear-input-button]' : 'resetForm'
        }
      };

  ClearInput = function($el, config) {
    ClearInput.baseConstructor.call(this, $el, config, defaultConfig);

    this.$resetInput = this.$el.find('[data-dough-clear-input]');
    this.$resetButton = this.$el.find('[data-dough-clear-input-button]');

    this.updateResetButton();
  };

  /**
  * Inherit from base module, for shared methods and interface
  */
  DoughBaseComponent.extend(ClearInput);

  /**
  * Set up and populate the model from the form inputs
  * @param {Promise} initialised
  */
  ClearInput.prototype.init = function(initialised) {
    this._initialisedSuccess(initialised);
  };

  ClearInput.prototype.updateResetButton = function() {
    var fn = this.$resetInput.val() === '' ? 'removeClass' : 'addClass';
    this.$resetButton[fn]('is-active');
  };

// We are progressively enhancing the form with JS. The CSS button, type 'reset'
// resets the form faster than the JS can run, so we need to invoke reset() to ensure the correct behaviour.

  ClearInput.prototype.resetForm = function(e) {
    this.$resetInput.val('');
    this.updateResetButton();
    this.$resetInput.focus();
    e.preventDefault();
  };

  return ClearInput;

});
<|endoftext|>


<commit_before>// Require React
React = require('react/addons');
var TagsInput = require('react-tagsinput');

// Material UI
import mui from 'material-ui';
let RaisedButton = mui.RaisedButton;
let Colors = mui.Styles.Colors;
let TextField = mui.TextField;
let ThemeManager = mui.Styles.ThemeManager;
let Snackbar = mui.Snackbar;
let LightRawTheme = mui.Styles.LightRawTheme;
let DropDownMenu = mui.DropDownMenu;
let injectTapEventPlugin = require("react-tap-event-plugin");

injectTapEventPlugin();

// Define component
const Search = React.createClass({
  mixins: [React.addons.LinkedStateMixin],
  childContextTypes: {
    muiTheme: React.PropTypes.object
  },

  getInitialState () {
    return {
      muiTheme: ThemeManager.getMuiTheme(LightRawTheme),
      current_filter: 'repos',
      tags: [],
      current_filter_placeholder: ">=100 or >10"
    };
  },

  componentWillMount() {
    let newMuiTheme = ThemeManager.modifyRawThemePalette(this.state.muiTheme, {
      accent1Color: Colors.deepOrange500
    });
    this.setState({muiTheme: newMuiTheme});
  },

  componentDidMount(){
    this._populateParams();
  },

  getChildContext() {
    return {
      muiTheme: this.state.muiTheme,
    };
  },

  render() {

    let textFieldStyles = {
      marginRight: '20px',
      marginTop: '10px'
    }

    let menuItems = [
       { payload: '1', text: 'repos', placeholder: '>= 5 or >10' },
       { payload: '2', text: 'location', placeholder: 'london or france, south america' },
       { payload: '3', text: 'language', placeholder: 'ruby or rust, c++' },
       { payload: '4', text: 'followers', placeholder: '>=100 or >1000' },
       { payload: '5', text: 'joined', placeholder: '>=2012-04-30 or >2012-04-29' },
    ];

    return (
        <form ref="search" method="GET" action={this.props.action} onKeyDown={this._handleKeyDown}>
          <TagsInput ref='tags' name="q" onTagAdd={this._addTag} />
          <RaisedButton label="Find" primary={true} onClick={this._handleSubmit} />
          <Snackbar
            ref="snackbar"
            message="Search can't be empty"
            action="error"
            autoHideDuration={5000} />
        </form>
      );
  },

  _filterChanged(e, select, menuItem) {
    this.setState({current_filter: menuItem.text, current_filter_placeholder: menuItem.placeholder});
  },

  _addTag(event) {
    // Add value to input
    $('.react-tagsinput-input').val(this.refs.tags.getTags().join(', '));
    // finally submit form
    var formData = $(this.refs.search.getDOMNode()).serialize();
    // Pre-fetch the page
    $.get('/members', formData, function(data) {
    }, "html");
  },

  _handleSubmit(event) {
    event.preventDefault();
    var query = this.refs.tags.getDOMNode().value;
    if (this.refs.tags.getTags().join(', ').length > 0) {
      // finally submit form
      var formData = $(this.refs.search.getDOMNode()).serialize();
      // Fetch members based on search
      this.props.searchMembers('/members/search', formData);
    } else {
      event.stopPropagation();
      this.refs.snackbar.show();
    }
  }

});

module.exports = Search;
<commit_msg>Remove unused method<commit_after>// Require React
React = require('react/addons');
var TagsInput = require('react-tagsinput');

// Material UI
import mui from 'material-ui';
let RaisedButton = mui.RaisedButton;
let Colors = mui.Styles.Colors;
let TextField = mui.TextField;
let ThemeManager = mui.Styles.ThemeManager;
let Snackbar = mui.Snackbar;
let LightRawTheme = mui.Styles.LightRawTheme;
let DropDownMenu = mui.DropDownMenu;
let injectTapEventPlugin = require("react-tap-event-plugin");

injectTapEventPlugin();

// Define component
const Search = React.createClass({
  mixins: [React.addons.LinkedStateMixin],
  childContextTypes: {
    muiTheme: React.PropTypes.object
  },

  getInitialState () {
    return {
      muiTheme: ThemeManager.getMuiTheme(LightRawTheme),
      current_filter: 'repos',
      tags: [],
      current_filter_placeholder: ">=100 or >10"
    };
  },

  componentWillMount() {
    let newMuiTheme = ThemeManager.modifyRawThemePalette(this.state.muiTheme, {
      accent1Color: Colors.deepOrange500
    });
    this.setState({muiTheme: newMuiTheme});
  },

  getChildContext() {
    return {
      muiTheme: this.state.muiTheme,
    };
  },

  render() {

    let textFieldStyles = {
      marginRight: '20px',
      marginTop: '10px'
    }

    let menuItems = [
       { payload: '1', text: 'repos', placeholder: '>= 5 or >10' },
       { payload: '2', text: 'location', placeholder: 'london or france, south america' },
       { payload: '3', text: 'language', placeholder: 'ruby or rust, c++' },
       { payload: '4', text: 'followers', placeholder: '>=100 or >1000' },
       { payload: '5', text: 'joined', placeholder: '>=2012-04-30 or >2012-04-29' },
    ];

    return (
        <form ref="search" method="GET" action={this.props.action} onKeyDown={this._handleKeyDown}>
          <TagsInput ref='tags' name="q" onTagAdd={this._addTag} />
          <RaisedButton label="Find" primary={true} onClick={this._handleSubmit} />
          <Snackbar
            ref="snackbar"
            message="Search can't be empty"
            action="error"
            autoHideDuration={5000} />
        </form>
      );
  },

  _filterChanged(e, select, menuItem) {
    this.setState({current_filter: menuItem.text, current_filter_placeholder: menuItem.placeholder});
  },

  _addTag(event) {
    // Add value to input
    $('.react-tagsinput-input').val(this.refs.tags.getTags().join(', '));
    // finally submit form
    var formData = $(this.refs.search.getDOMNode()).serialize();
    // Pre-fetch the page
    $.get('/members', formData, function(data) {
    }, "html");
  },

  _handleSubmit(event) {
    event.preventDefault();
    var query = this.refs.tags.getDOMNode().value;
    if (this.refs.tags.getTags().join(', ').length > 0) {
      // finally submit form
      var formData = $(this.refs.search.getDOMNode()).serialize();
      // Fetch members based on search
      this.props.searchMembers('/members/search', formData);
    } else {
      event.stopPropagation();
      this.refs.snackbar.show();
    }
  }

});

module.exports = Search;
<|endoftext|>


<commit_before>/*global jQuery,cyberDojo*/
'use strict';
var cyberDojo = (function(cd, $) {

  cd.setupAvatarNameHoverTip = ($element, textBefore, avatarIndex, textAfter) => {
    cd.setTip($element, () => {
      $.getJSON('/images/avatars/names.json', '', (avatarsNames) => {
        const avatarName = avatarsNames[avatarIndex];
        const tip = `${textBefore}${avatarName}${textAfter}`;
        cd.showHoverTip($element, tip);
      });
    });
  };

  // - - - - - - - - - - - - - - - - - - - -

  cd.setupTrafficLightTip = ($light, kataId, wasIndex, nowIndex) => {
    cd.setTip($light, () => {
      const args = `id=${kataId}&was_index=${wasIndex}&now_index=${nowIndex}`;
      $.getJSON(`/differ/diff_summary2?${args}`, '', (data) => {
        cd.showHoverTip($light, tipHtml($light, data.diff_summary2));
      });
    });
  };

  // - - - - - - - - - - - - - - - - - - - -

  const tipHtml = ($light, diffSummary) => {
    const avatarIndex = $light.data('avatar-index');
    const colour = $light.data('colour');
    const number = $light.data('number');
    return `

               ${avatarImage(avatarIndex)}
               ${number}

           ${diffLinesHtmlTable(diffSummary)}`;
  };

  // - - - - - - - - - - - - - - - - - - - -

  const avatarImage = (avatarIndex) => {
    if (avatarIndex === '') {
      return '';
    } else {
      return ``;
    }
  };

  // - - - - - - - - - - - - - - - - - - - -

  const diffLinesHtmlTable = (files) => {
    const $table = $('
');
    if (files.length > 0) {
      const $tr = $('
');
      $tr.append($linesDeletedCountIcon());
      $tr.append($linesAddedCountIcon())
      $table.append($tr);
    }
    files.forEach(function(file) {
      $table.append(
        `

              ${nonZero(file.line_counts.deleted)}

              ${nonZero(file.line_counts.added)}

              ${diffFilename(file)}

`
      ); //append
    }); // forEach
    return $table.get(0).outerHTML;
  };

  const nonZero = (n) => {
    return n > 0 ? n : ' ';
  };

  const diffFilename = (file) => {
    if (diffType(file) === 'deleted') {
      return file.old_filename;
    } else {
      return file.new_filename;
    }
  };

  const diffType = (file) => {
    // currently diff_summary() does not send info on unchanged files.
    if (deletedFile(file)) {
      return 'deleted';
    } else if (createdFile(file)) {
      return 'created';
    } else if (renamedFile(file)) {
      return 'renamed';
    } else {
      return 'changed';
    }
  };

  const deletedFile = (file) => {
    return file.new_filename === null;
  };
  const createdFile = (file) => {
    return file.old_filename === null;
  };
  const renamedFile = (file) => {
    return file.old_filename != file.new_filename; // and neither are null
  };

  const $linesDeletedCountIcon = () => {
    const $icon = $('
', { class:'diff-deleted-line-count-icon' }).html('—');
    return $('').append($icon);
  };

  const $linesAddedCountIcon = () => {
    const $icon = $('
', { class:'diff-added-line-count-icon' }).text('+');
    return $('').append($icon);
  };

  // - - - - - - - - - - - - - - - - - - - -

  cd.setupHoverTips = function(nodes) {
    nodes.each(function() {
      const node = $(this);
      const setTipCallBack = () => {
        const tip = node.data('tip');
        if (tip === 'traffic_light_count') {
          cd.showHoverTip(node, trafficLightCountHoverTip(node));
        } else {
          cd.showHoverTip(node, tip);
        }
      };
      cd.setTip(node, setTipCallBack);
    });
  };

  // - - - - - - - - - - - - - - - - - - - -

  const trafficLightCountHoverTip = (node) => {
    // for dashboard avatar totalCount
    const reds = node.data('red-count');
    const ambers = node.data('amber-count');
    const greens = node.data('green-count');
    const timeOuts = node.data('timed-out-count');
    const tr = (s) => `<tr>${s}</tr>`;
    const td = (s) => `<td>${s}</td>`;
    const trLight = (colour, count) => {
      return tr(td('<img' +
                   " class='traffic-light-diff-tip-traffic-light-image'" +
                   ` src='/images/traffic-light/${colour}.png'>`) +
                td(`<div class='traffic-light-diff-tip-tag ${colour}'>` +
                   count +
                   '
'));
    };
    let html = '';
    html += '
';
    html += trLight('red', reds);
    html += trLight('amber', ambers);
    html += trLight('green', greens);
    if (timeOuts > 0) {
      html += trLight('timed_out', timeOuts);
    }
    html += '
';
    return html;
  };

  // - - - - - - - - - - - - - - - - - - - -

  const hoverTipContainer = () => {
    return $('#hover-tip-container');
  };

  cd.setTip = (node, setTipCallBack) => {
    // The speed of the mouse could easily exceed
    // the speed of the getJSON callback...
    // The mouse-has-left attribute caters for this.
    node.mouseenter(() => {
      node.removeClass('mouse-has-left');
      setTipCallBack(node);
    });
    node.mouseleave(() => {
      node.addClass('mouse-has-left');
      hoverTipContainer().empty();
    });
  };

  // - - - - - - - - - - - - - - - - - - - -

  cd.createTip = (element, tip, where) => {
    cd.setTip(element, () => {
      cd.showHoverTip(element, tip, where);
    });
  };

  // - - - - - - - - - - - - - - - - - - - -

  cd.showHoverTip = (node, tip, where) => {
    if (where === undefined) {
      where = {};
    }
    if (where.my === undefined) { where.my = 'top'; }
    if (where.at === undefined) { where.at = 'bottom'; }
    if (where.of === undefined) { where.of = node; }

    if (!node.attr('disabled')) {
      if (!node.hasClass('mouse-has-left')) {
        // position() is the jQuery UI plug-in
        // https://jqueryui.com/position/
        const hoverTip = $('
', {
          'class': 'hover-tip'
        }).html(tip).position({
          my: where.my,
          at: where.at,
          of: where.of,
          collision: 'flip'
        });
        hoverTipContainer().html(hoverTip);
      }
    }
  };

  // - - - - - - - - - - - - - - - - - - - -

  return cd;

})(cyberDojo || {}, jQuery);
<commit_msg>Simplify traffic-light hover-tip now differ.diff_summary() send diff type<commit_after>/*global jQuery,cyberDojo*/
'use strict';
var cyberDojo = (function(cd, $) {

  cd.setupAvatarNameHoverTip = ($element, textBefore, avatarIndex, textAfter) => {
    cd.setTip($element, () => {
      $.getJSON('/images/avatars/names.json', '', (avatarsNames) => {
        const avatarName = avatarsNames[avatarIndex];
        const tip = `${textBefore}${avatarName}${textAfter}`;
        cd.showHoverTip($element, tip);
      });
    });
  };

  // - - - - - - - - - - - - - - - - - - - -

  cd.setupTrafficLightTip = ($light, kataId, wasIndex, nowIndex) => {
    cd.setTip($light, () => {
      const args = `id=${kataId}&was_index=${wasIndex}&now_index=${nowIndex}`;
      $.getJSON(`/differ/diff_summary2?${args}`, '', (data) => {
        cd.showHoverTip($light, tipHtml($light, data.diff_summary2));
      });
    });
  };

  // - - - - - - - - - - - - - - - - - - - -

  const tipHtml = ($light, diffSummary) => {
    const avatarIndex = $light.data('avatar-index');
    const colour = $light.data('colour');
    const number = $light.data('number');
    return `

               ${avatarImage(avatarIndex)}
               ${number}

           ${diffLinesHtmlTable(diffSummary)}`;
  };

  // - - - - - - - - - - - - - - - - - - - -

  const avatarImage = (avatarIndex) => {
    if (avatarIndex === '') {
      return '';
    } else {
      return ``;
    }
  };

  // - - - - - - - - - - - - - - - - - - - -

  const diffLinesHtmlTable = (files) => {
    const $table = $('
');
    if (files.length > 0) {
      const $tr = $('
');
      $tr.append($linesDeletedCountIcon());
      $tr.append($linesAddedCountIcon())
      $table.append($tr);
    }
    files.forEach(function(file) {
      $table.append(
        `

              ${nonZero(file.line_counts.deleted)}

              ${nonZero(file.line_counts.added)}

              ${diffFilename(file)}

`
      ); //append
    }); // forEach
    return $table.get(0).outerHTML;
  };

  const nonZero = (n) => {
    return n > 0 ? n : ' ';
  };

  const diffFilename = (diff) => {
    if (diff.type === 'deleted') {
      return diff.old_filename;
    } else {
      return diff.new_filename;
    }
  };

  const $linesDeletedCountIcon = () => {
    const $icon = $('
', { class:'diff-deleted-line-count-icon' }).html('—');
    return $('').append($icon);
  };

  const $linesAddedCountIcon = () => {
    const $icon = $('
', { class:'diff-added-line-count-icon' }).text('+');
    return $('').append($icon);
  };

  // - - - - - - - - - - - - - - - - - - - -

  cd.setupHoverTips = function(nodes) {
    nodes.each(function() {
      const node = $(this);
      const setTipCallBack = () => {
        const tip = node.data('tip');
        if (tip === 'traffic_light_count') {
          cd.showHoverTip(node, trafficLightCountHoverTip(node));
        } else {
          cd.showHoverTip(node, tip);
        }
      };
      cd.setTip(node, setTipCallBack);
    });
  };

  // - - - - - - - - - - - - - - - - - - - -

  const trafficLightCountHoverTip = (node) => {
    // for dashboard avatar totalCount
    const reds = node.data('red-count');
    const ambers = node.data('amber-count');
    const greens = node.data('green-count');
    const timeOuts = node.data('timed-out-count');
    const tr = (s) => `<tr>${s}</tr>`;
    const td = (s) => `<td>${s}</td>`;
    const trLight = (colour, count) => {
      return tr(td('<img' +
                   " class='traffic-light-diff-tip-traffic-light-image'" +
                   ` src='/images/traffic-light/${colour}.png'>`) +
                td(`<div class='traffic-light-diff-tip-tag ${colour}'>` +
                   count +
                   '
'));
    };
    let html = '';
    html += '
';
    html += trLight('red', reds);
    html += trLight('amber', ambers);
    html += trLight('green', greens);
    if (timeOuts > 0) {
      html += trLight('timed_out', timeOuts);
    }
    html += '
';
    return html;
  };

  // - - - - - - - - - - - - - - - - - - - -

  const hoverTipContainer = () => {
    return $('#hover-tip-container');
  };

  cd.setTip = (node, setTipCallBack) => {
    // The speed of the mouse could easily exceed
    // the speed of the getJSON callback...
    // The mouse-has-left attribute caters for this.
    node.mouseenter(() => {
      node.removeClass('mouse-has-left');
      setTipCallBack(node);
    });
    node.mouseleave(() => {
      node.addClass('mouse-has-left');
      hoverTipContainer().empty();
    });
  };

  // - - - - - - - - - - - - - - - - - - - -

  cd.createTip = (element, tip, where) => {
    cd.setTip(element, () => {
      cd.showHoverTip(element, tip, where);
    });
  };

  // - - - - - - - - - - - - - - - - - - - -

  cd.showHoverTip = (node, tip, where) => {
    if (where === undefined) {
      where = {};
    }
    if (where.my === undefined) { where.my = 'top'; }
    if (where.at === undefined) { where.at = 'bottom'; }
    if (where.of === undefined) { where.of = node; }

    if (!node.attr('disabled')) {
      if (!node.hasClass('mouse-has-left')) {
        // position() is the jQuery UI plug-in
        // https://jqueryui.com/position/
        const hoverTip = $('
', {
          'class': 'hover-tip'
        }).html(tip).position({
          my: where.my,
          at: where.at,
          of: where.of,
          collision: 'flip'
        });
        hoverTipContainer().html(hoverTip);
      }
    }
  };

  // - - - - - - - - - - - - - - - - - - - -

  return cd;

})(cyberDojo || {}, jQuery);
<|endoftext|>


<commit_before>if ( ! Detector.webgl ) Detector.addGetWebGLMessage();

var camera, scene, renderer;

init();

function init() {

  scene = new THREE.Scene();
  scene.add( new THREE.AmbientLight( 0x999999 ) );

  camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 1, 500 );

  // Z is up for objects intended to be 3D printed.

  camera.up.set( 0, 0, 1 );
  camera.position.set( 0, -9, 6 );

  camera.add( new THREE.PointLight( 0xffffff, 0.8 ) );

  scene.add( camera );

  renderer = new THREE.WebGLRenderer( { antialias: true } );
  renderer.setClearColor( 0x999999 );
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize( window.innerWidth, window.innerHeight );

  var loader = new THREE.STLLoader();

  // Binary files

  var material = new THREE.MeshPhongMaterial( { color: 0x0e2045, specular: 0x111111, shininess: 200 } );
  loader.load( 'https://s3.amazonaws.com/limbforgestls/EbeArm/Ebe_forearm_L/forearm_L_C4-200_L1-230.stl', function ( geometry ) {
    var mesh = new THREE.Mesh( geometry, material );

    mesh.position.set( 0, 0, 0 );
    mesh.rotation.set( 0, 0, 0 );
    mesh.scale.set( .02, .02, .02 );

    mesh.castShadow = true;
    mesh.receiveShadow = true;

    render();
  });

  var controls = new THREE.OrbitControls( camera, renderer.domElement );
  controls.addEventListener( 'change', render );
  controls.target.set( 0, 1.2, 2 );
  controls.update();
  window.addEventListener( 'resize', onWindowResize, false );

}

function onWindowResize() {

  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize( window.innerWidth, window.innerHeight );

  render();

}

function render() {

  renderer.render( scene, camera );

}

  $(document).on('turbolinks:load', function() {
    document.getElementById('display-hand').appendChild( renderer.domElement );
    render();
  });
<commit_msg>adding more lights to .stl viewer<commit_after>if ( ! Detector.webgl ) Detector.addGetWebGLMessage();

var camera, scene, renderer;

init();

function init() {

  scene = new THREE.Scene();
  scene.add( new THREE.AmbientLight( 0x999999 ) );

  camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 1, 500 );

  // Z is up for objects intended to be 3D printed.

  camera.up.set( 0, 0, 1 );
  camera.position.set( 0, -9, 6 );

  camera.add( new THREE.PointLight( 0xffffff, 1 ) );
  camera.add( new THREE.PointLight( 0xffffff, 1 ) );
  camera.add( new THREE.PointLight( 0xffffff, 1 ) );

  scene.add( camera );

  renderer = new THREE.WebGLRenderer( { antialias: true } );
  renderer.setClearColor( 0x999999 );
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize( window.innerWidth, window.innerHeight );

  var loader = new THREE.STLLoader();

  // Binary files

  var material = new THREE.MeshPhongMaterial( { color: 0x0e2045, specular: 0x111111, shininess: 200 } );
  loader.load( 'https://s3.amazonaws.com/limbforgestls/EbeArm/Ebe_forearm_L/forearm_L_C4-200_L1-230.stl', function ( geometry ) {
    var mesh = new THREE.Mesh( geometry, material );

    mesh.position.set( 0, 0, 0 );
    mesh.rotation.set( 0, 0, 0 );
    mesh.scale.set( .02, .02, .02 );

    mesh.castShadow = true;
    mesh.receiveShadow = true;

    render();
  });

  var controls = new THREE.OrbitControls( camera, renderer.domElement );
  controls.addEventListener( 'change', render );
  controls.target.set( 0, 1.2, 2 );
  controls.update();
  window.addEventListener( 'resize', onWindowResize, false );

}

function onWindowResize() {

  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize( window.innerWidth, window.innerHeight );

  render();

}

function render() {

  renderer.render( scene, camera );

}

  $(document).on('turbolinks:load', function() {
    document.getElementById('display-hand').appendChild( renderer.domElement );
    render();
  });
<|endoftext|>


<commit_before>// XXX docs

// Things we explicitly do NOT support:
//    - heterogenous arrays

var currentArgumentChecker = new Meteor.EnvironmentVariable;
var isPlainObject = require("./isPlainObject.js").isPlainObject;

/**
 * @summary Check that a value matches a [pattern](#matchpatterns).
 * If the value does not match the pattern, throw a `Match.Error`.
 *
 * Particularly useful to assert that arguments to a function have the right
 * types and structure.
 * @locus Anywhere
 * @param {Any} value The value to check
 * @param {MatchPattern} pattern The pattern to match
 * `value` against
 */
var check = exports.check = function (value, pattern) {
  // Record that check got called, if somebody cared.
  //
  // We use getOrNullIfOutsideFiber so that it's OK to call check()
  // from non-Fiber server contexts; the downside is that if you forget to
  // bindEnvironment on some random callback in your method/publisher,
  // it might not find the argumentChecker and you'll get an error about
  // not checking an argument that it looks like you're checking (instead
  // of just getting a "Node code must run in a Fiber" error).
  var argChecker = currentArgumentChecker.getOrNullIfOutsideFiber();
  if (argChecker)
    argChecker.checking(value);
  var result = testSubtree(value, pattern);
  if (result) {
    var err = new Match.Error(result.message);
    if (result.path) {
      err.message += " in field " + result.path;
      err.path = result.path;
    }
    throw err;
  }
};

/**
 * @namespace Match
 * @summary The namespace for all Match types and methods.
 */
var Match = exports.Match = {
  Optional: function (pattern) {
    return new Optional(pattern);
  },
  Maybe: function (pattern) {
    return new Maybe(pattern);
  },
  OneOf: function (...args) {
    return new OneOf(args);
  },
  Any: ['__any__'],
  Where: function (condition) {
    return new Where(condition);
  },
  ObjectIncluding: function (pattern) {
    return new ObjectIncluding(pattern);
  },
  ObjectWithValues: function (pattern) {
    return new ObjectWithValues(pattern);
  },
  // Matches only signed 32-bit integers
  Integer: ['__integer__'],

  // XXX matchers should know how to describe themselves for errors
  Error: Meteor.makeErrorType("Match.Error", function (msg) {
    this.message = "Match error: " + msg;
    // The path of the value that failed to match. Initially empty, this gets
    // populated by catching and rethrowing the exception as it goes back up the
    // stack.
    // E.g.: "vals[3].entity.created"
    this.path = "";
    // If this gets sent over DDP, don't give full internal details but at least
    // provide something better than 500 Internal server error.
    this.sanitizedError = new Meteor.Error(400, "Match failed");
  }),

  // Tests to see if value matches pattern. Unlike check, it merely returns true
  // or false (unless an error other than Match.Error was thrown). It does not
  // interact with _failIfArgumentsAreNotAllChecked.
  // XXX maybe also implement a Match.match which returns more information about
  //     failures but without using exception handling or doing what check()
  //     does with _failIfArgumentsAreNotAllChecked and Meteor.Error conversion

  /**
   * @summary Returns true if the value matches the pattern.
   * @locus Anywhere
   * @param {Any} value The value to check
   * @param {MatchPattern} pattern The pattern to match `value` against
   */
  test: function (value, pattern) {
    return !testSubtree(value, pattern);
  },

  // Runs `f.apply(context, args)`. If check() is not called on every element of
  // `args` (either directly or in the first level of an array), throws an error
  // (using `description` in the message).
  //
  _failIfArgumentsAreNotAllChecked: function (f, context, args, description) {
    var argChecker = new ArgumentChecker(args, description);
    var result = currentArgumentChecker.withValue(argChecker, function () {
      return f.apply(context, args);
    });
    // If f didn't itself throw, make sure it checked all of its arguments.
    argChecker.throwUnlessAllArgumentsHaveBeenChecked();
    return result;
  }
};

var Optional = function (pattern) {
  this.pattern = pattern;
};

var Maybe = function (pattern) {
  this.pattern = pattern;
};

var OneOf = function (choices) {
  if (!choices || choices.length === 0)
    throw new Error("Must provide at least one choice to Match.OneOf");
  this.choices = choices;
};

var Where = function (condition) {
  this.condition = condition;
};

var ObjectIncluding = function (pattern) {
  this.pattern = pattern;
};

var ObjectWithValues = function (pattern) {
  this.pattern = pattern;
};

var stringForErrorMessage = function (value, options) {
  options = options || {};

  if ( value === null ) return "null";

  if ( options.onlyShowType ) {
    return typeof value;
  }

  // Your average non-object things.  Saves from doing the try/catch below for.
  if ( typeof value !== "object" ) {
    return EJSON.stringify(value)
  }

  try {
    // Find objects with circular references since EJSON doesn't support them yet (Issue #4778 + Unaccepted PR)
    // If the native stringify is going to choke, EJSON.stringify is going to choke too.
    JSON.stringify(value);
  } catch (stringifyError) {
    if ( stringifyError.name === "TypeError" ) {
      return typeof value;
    }
  }

  return EJSON.stringify(value);
};

var typeofChecks = [
  [String, "string"],
  [Number, "number"],
  [Boolean, "boolean"],
  // While we don't allow undefined/function in EJSON, this is good for optional
  // arguments with OneOf.
  [Function, "function"],
  [undefined, "undefined"]
];

// Return `false` if it matches. Otherwise, return an object with a `message` and a `path` field.
var testSubtree = function (value, pattern) {
  // Match anything!
  if (pattern === Match.Any)
    return false;

  // Basic atomic types.
  // Do not match boxed objects (e.g. String, Boolean)
  for (var i = 0; i < typeofChecks.length; ++i) {
    if (pattern === typeofChecks[i][0]) {
      if (typeof value === typeofChecks[i][1])
        return false;
      return {
        message: "Expected " + typeofChecks[i][1] + ", got " + stringForErrorMessage(value, { onlyShowType: true }),
        path: ""
      };
    }
  }

  if (pattern === null) {
    if (value === null) {
      return false;
    }
    return {
      message: "Expected null, got " + stringForErrorMessage(value),
      path: ""
    };
  }

  // Strings, numbers, and booleans match literally. Goes well with Match.OneOf.
  if (typeof pattern === "string" || typeof pattern === "number" || typeof pattern === "boolean") {
    if (value === pattern)
      return false;
    return {
      message: "Expected " + pattern + ", got " + stringForErrorMessage(value),
      path: ""
    };
  }

  // Match.Integer is special type encoded with array
  if (pattern === Match.Integer) {
    // There is no consistent and reliable way to check if variable is a 64-bit
    // integer. One of the popular solutions is to get reminder of division by 1
    // but this method fails on really large floats with big precision.
    // E.g.: 1.348192308491824e+23 % 1 === 0 in V8
    // Bitwise operators work consistantly but always cast variable to 32-bit
    // signed integer according to JavaScript specs.
    if (typeof value === "number" && (value | 0) === value)
      return false;
    return {
      message: "Expected Integer, got " + stringForErrorMessage(value),
      path: ""
    };
  }

  // "Object" is shorthand for Match.ObjectIncluding({});
  if (pattern === Object)
    pattern = Match.ObjectIncluding({});

  // Array (checked AFTER Any, which is implemented as an Array).
  if (pattern instanceof Array) {
    if (pattern.length !== 1) {
      return {
        message: "Bad pattern: arrays must have one type element" + stringForErrorMessage(pattern),
        path: ""
      };
    }
    if (!Array.isArray(value) && !isArguments(value)) {
      return {
        message: "Expected array, got " + stringForErrorMessage(value),
        path: ""
      };
    }

    for (var i = 0, length = value.length; i < length; i++) {
      var result = testSubtree(value[i], pattern[0]);
      if (result) {
        result.path = _prependPath(i, result.path);
        return result;
      }
    }
    return false;
  }

  // Arbitrary validation checks. The condition can return false or throw a
  // Match.Error (ie, it can internally use check()) to fail.
  if (pattern instanceof Where) {
    var result;
    try {
      result = pattern.condition(value);
    } catch (err) {
      if (!(err instanceof Match.Error))
        throw err;
      return {
        message: err.message,
        path: err.path
      };
    }
    if (result)
      return false;
    // XXX this error is terrible
    return {
      message: "Failed Match.Where validation",
      path: ""
    };
  }

  if (pattern instanceof Maybe) {
    pattern = Match.OneOf(undefined, null, pattern.pattern);
  }
  else if (pattern instanceof Optional) {
    pattern = Match.OneOf(undefined, pattern.pattern);
  }

  if (pattern instanceof OneOf) {
    for (var i = 0; i < pattern.choices.length; ++i) {
      var result = testSubtree(value, pattern.choices[i]);
      if (!result) {
        // No error? Yay, return.
        return false;
      }
      // Match errors just mean try another choice.
    }
    // XXX this error is terrible
    return {
      message: "Failed Match.OneOf, Match.Maybe or Match.Optional validation",
      path: ""
    };
  }

  // A function that isn't something we special-case is assumed to be a
  // constructor.
  if (pattern instanceof Function) {
    if (value instanceof pattern)
      return false;
    return {
      message: "Expected " + (pattern.name ||"particular constructor"),
      path: ""
    };
  }

  var unknownKeysAllowed = false;
  var unknownKeyPattern;
  if (pattern instanceof ObjectIncluding) {
    unknownKeysAllowed = true;
    pattern = pattern.pattern;
  }
  if (pattern instanceof ObjectWithValues) {
    unknownKeysAllowed = true;
    unknownKeyPattern = [pattern.pattern];
    pattern = {};  // no required keys
  }

  if (typeof pattern !== "object") {
    return {
      message: "Bad pattern: unknown pattern type",
      path: ""
    };
  }

  // An object, with required and optional keys. Note that this does NOT do
  // structural matches against objects of special types that happen to match
  // the pattern: this really needs to be a plain old {Object}!
  if (typeof value !== 'object') {
    return {
      message: "Expected object, got " + typeof value,
      path: ""
    };
  }
  if (value === null) {
    return {
      message: "Expected object, got null",
      path: ""
    };
  }
  if (! isPlainObject(value)) {
    return {
      message: "Expected plain object",
      path: ""
    };
  }

  var requiredPatterns = {};
  var optionalPatterns = {};
  _.each(pattern, function (subPattern, key) {
    if (subPattern instanceof Optional || subPattern instanceof Maybe)
      optionalPatterns[key] = subPattern.pattern;
    else
      requiredPatterns[key] = subPattern;
  });

  //XXX: replace with underscore's _.allKeys if Meteor updates underscore to 1.8+ (or lodash)
  var allKeys = function(obj){
    var keys = [];
    if (_.isObject(obj)){
      for (var key in obj) keys.push(key);
    }
    return keys;
  }

  for (var keys = allKeys(value), i = 0, length = keys.length; i < length; i++) {
    var key = keys[i];
    var subValue = value[key];
    if (Object.prototype.hasOwnProperty.call(requiredPatterns, key)) {
      var result = testSubtree(subValue, requiredPatterns[key]);
      if (result) {
        result.path = _prependPath(key, result.path);
        return result;
      }
      delete requiredPatterns[key];
    } else if (Object.prototype.hasOwnProperty.call(optionalPatterns, key)) {
      var result = testSubtree(subValue, optionalPatterns[key]);
      if (result) {
        result.path = _prependPath(key, result.path);
        return result;
      }
    } else {
      if (!unknownKeysAllowed) {
        return {
          message: "Unknown key",
          path: key
        };
      }
      if (unknownKeyPattern) {
        var result = testSubtree(subValue, unknownKeyPattern[0]);
        if (result) {
          result.path = _prependPath(key, result.path);
          return result;
        }
      }
    }
  }

  var keys = Object.keys(requiredPatterns);
  if (keys.length) {
    return {
      message: "Missing key '" + keys[0] + "'",
      path: ""
    };
  }
};

class ArgumentChecker {
  constructor (args, description) {
    var self = this;
    // Make a SHALLOW copy of the arguments. (We'll be doing identity checks
    // against its contents.)
    self.args = [...args];
    // Since the common case will be to check arguments in order, and we splice
    // out arguments when we check them, make it so we splice out from the end
    // rather than the beginning.
    self.args.reverse();
    self.description = description;
  }

  checking(value) {
    var self = this;
    if (self._checkingOneValue(value))
      return;
    // Allow check(arguments, [String]) or check(arguments.slice(1), [String])
    // or check([foo, bar], [String]) to count... but only if value wasn't
    // itself an argument.
    if (Array.isArray(value) || isArguments(value)) {
      Array.prototype.forEach.call(value, self._checkingOneValue.bind(self));
    }
  }

  _checkingOneValue(value) {
    var self = this;
    for (var i = 0; i < self.args.length; ++i) {
      // Is this value one of the arguments? (This can have a false positive if
      // the argument is an interned primitive, but it's still a good enough
      // check.)
      // (NaN is not === to itself, so we have to check specially.)
      if (value === self.args[i] ||
          (Number.isNaN(value) && Number.isNaN(self.args[i]))) {
        self.args.splice(i, 1);
        return true;
      }
    }
    return false;
  }

  throwUnlessAllArgumentsHaveBeenChecked() {
    var self = this;
    if (self.args.length > 0)
      throw new Error("Did not check() all arguments during " +
                      self.description);
  }
}

var _jsKeywords = ["do", "if", "in", "for", "let", "new", "try", "var", "case",
  "else", "enum", "eval", "false", "null", "this", "true", "void", "with",
  "break", "catch", "class", "const", "super", "throw", "while", "yield",
  "delete", "export", "import", "public", "return", "static", "switch",
  "typeof", "default", "extends", "finally", "package", "private", "continue",
  "debugger", "function", "arguments", "interface", "protected", "implements",
  "instanceof"];

// Assumes the base of path is already escaped properly
// returns key + base
var _prependPath = function (key, base) {
  if ((typeof key) === "number" || key.match(/^[0-9]+$/))
    key = "[" + key + "]";
  else if (!key.match(/^[a-z_$][0-9a-z_$]*$/i) || _jsKeywords.includes(key))
    key = JSON.stringify([key]);

  if (base && base[0] !== "[")
    return key + '.' + base;
  return key + base;
};

function isArguments(item) {
  return Object.prototype.toString.call(item) === '[object Arguments]';
}
<commit_msg>Replace _.isObject<commit_after>// XXX docs

// Things we explicitly do NOT support:
//    - heterogenous arrays

var currentArgumentChecker = new Meteor.EnvironmentVariable;
var isPlainObject = require("./isPlainObject.js").isPlainObject;

/**
 * @summary Check that a value matches a [pattern](#matchpatterns).
 * If the value does not match the pattern, throw a `Match.Error`.
 *
 * Particularly useful to assert that arguments to a function have the right
 * types and structure.
 * @locus Anywhere
 * @param {Any} value The value to check
 * @param {MatchPattern} pattern The pattern to match
 * `value` against
 */
var check = exports.check = function (value, pattern) {
  // Record that check got called, if somebody cared.
  //
  // We use getOrNullIfOutsideFiber so that it's OK to call check()
  // from non-Fiber server contexts; the downside is that if you forget to
  // bindEnvironment on some random callback in your method/publisher,
  // it might not find the argumentChecker and you'll get an error about
  // not checking an argument that it looks like you're checking (instead
  // of just getting a "Node code must run in a Fiber" error).
  var argChecker = currentArgumentChecker.getOrNullIfOutsideFiber();
  if (argChecker)
    argChecker.checking(value);
  var result = testSubtree(value, pattern);
  if (result) {
    var err = new Match.Error(result.message);
    if (result.path) {
      err.message += " in field " + result.path;
      err.path = result.path;
    }
    throw err;
  }
};

/**
 * @namespace Match
 * @summary The namespace for all Match types and methods.
 */
var Match = exports.Match = {
  Optional: function (pattern) {
    return new Optional(pattern);
  },
  Maybe: function (pattern) {
    return new Maybe(pattern);
  },
  OneOf: function (...args) {
    return new OneOf(args);
  },
  Any: ['__any__'],
  Where: function (condition) {
    return new Where(condition);
  },
  ObjectIncluding: function (pattern) {
    return new ObjectIncluding(pattern);
  },
  ObjectWithValues: function (pattern) {
    return new ObjectWithValues(pattern);
  },
  // Matches only signed 32-bit integers
  Integer: ['__integer__'],

  // XXX matchers should know how to describe themselves for errors
  Error: Meteor.makeErrorType("Match.Error", function (msg) {
    this.message = "Match error: " + msg;
    // The path of the value that failed to match. Initially empty, this gets
    // populated by catching and rethrowing the exception as it goes back up the
    // stack.
    // E.g.: "vals[3].entity.created"
    this.path = "";
    // If this gets sent over DDP, don't give full internal details but at least
    // provide something better than 500 Internal server error.
    this.sanitizedError = new Meteor.Error(400, "Match failed");
  }),

  // Tests to see if value matches pattern. Unlike check, it merely returns true
  // or false (unless an error other than Match.Error was thrown). It does not
  // interact with _failIfArgumentsAreNotAllChecked.
  // XXX maybe also implement a Match.match which returns more information about
  //     failures but without using exception handling or doing what check()
  //     does with _failIfArgumentsAreNotAllChecked and Meteor.Error conversion

  /**
   * @summary Returns true if the value matches the pattern.
   * @locus Anywhere
   * @param {Any} value The value to check
   * @param {MatchPattern} pattern The pattern to match `value` against
   */
  test: function (value, pattern) {
    return !testSubtree(value, pattern);
  },

  // Runs `f.apply(context, args)`. If check() is not called on every element of
  // `args` (either directly or in the first level of an array), throws an error
  // (using `description` in the message).
  //
  _failIfArgumentsAreNotAllChecked: function (f, context, args, description) {
    var argChecker = new ArgumentChecker(args, description);
    var result = currentArgumentChecker.withValue(argChecker, function () {
      return f.apply(context, args);
    });
    // If f didn't itself throw, make sure it checked all of its arguments.
    argChecker.throwUnlessAllArgumentsHaveBeenChecked();
    return result;
  }
};

var Optional = function (pattern) {
  this.pattern = pattern;
};

var Maybe = function (pattern) {
  this.pattern = pattern;
};

var OneOf = function (choices) {
  if (!choices || choices.length === 0)
    throw new Error("Must provide at least one choice to Match.OneOf");
  this.choices = choices;
};

var Where = function (condition) {
  this.condition = condition;
};

var ObjectIncluding = function (pattern) {
  this.pattern = pattern;
};

var ObjectWithValues = function (pattern) {
  this.pattern = pattern;
};

var stringForErrorMessage = function (value, options) {
  options = options || {};

  if ( value === null ) return "null";

  if ( options.onlyShowType ) {
    return typeof value;
  }

  // Your average non-object things.  Saves from doing the try/catch below for.
  if ( typeof value !== "object" ) {
    return EJSON.stringify(value)
  }

  try {
    // Find objects with circular references since EJSON doesn't support them yet (Issue #4778 + Unaccepted PR)
    // If the native stringify is going to choke, EJSON.stringify is going to choke too.
    JSON.stringify(value);
  } catch (stringifyError) {
    if ( stringifyError.name === "TypeError" ) {
      return typeof value;
    }
  }

  return EJSON.stringify(value);
};

var typeofChecks = [
  [String, "string"],
  [Number, "number"],
  [Boolean, "boolean"],
  // While we don't allow undefined/function in EJSON, this is good for optional
  // arguments with OneOf.
  [Function, "function"],
  [undefined, "undefined"]
];

// Return `false` if it matches. Otherwise, return an object with a `message` and a `path` field.
var testSubtree = function (value, pattern) {
  // Match anything!
  if (pattern === Match.Any)
    return false;

  // Basic atomic types.
  // Do not match boxed objects (e.g. String, Boolean)
  for (var i = 0; i < typeofChecks.length; ++i) {
    if (pattern === typeofChecks[i][0]) {
      if (typeof value === typeofChecks[i][1])
        return false;
      return {
        message: "Expected " + typeofChecks[i][1] + ", got " + stringForErrorMessage(value, { onlyShowType: true }),
        path: ""
      };
    }
  }

  if (pattern === null) {
    if (value === null) {
      return false;
    }
    return {
      message: "Expected null, got " + stringForErrorMessage(value),
      path: ""
    };
  }

  // Strings, numbers, and booleans match literally. Goes well with Match.OneOf.
  if (typeof pattern === "string" || typeof pattern === "number" || typeof pattern === "boolean") {
    if (value === pattern)
      return false;
    return {
      message: "Expected " + pattern + ", got " + stringForErrorMessage(value),
      path: ""
    };
  }

  // Match.Integer is special type encoded with array
  if (pattern === Match.Integer) {
    // There is no consistent and reliable way to check if variable is a 64-bit
    // integer. One of the popular solutions is to get reminder of division by 1
    // but this method fails on really large floats with big precision.
    // E.g.: 1.348192308491824e+23 % 1 === 0 in V8
    // Bitwise operators work consistantly but always cast variable to 32-bit
    // signed integer according to JavaScript specs.
    if (typeof value === "number" && (value | 0) === value)
      return false;
    return {
      message: "Expected Integer, got " + stringForErrorMessage(value),
      path: ""
    };
  }

  // "Object" is shorthand for Match.ObjectIncluding({});
  if (pattern === Object)
    pattern = Match.ObjectIncluding({});

  // Array (checked AFTER Any, which is implemented as an Array).
  if (pattern instanceof Array) {
    if (pattern.length !== 1) {
      return {
        message: "Bad pattern: arrays must have one type element" + stringForErrorMessage(pattern),
        path: ""
      };
    }
    if (!Array.isArray(value) && !isArguments(value)) {
      return {
        message: "Expected array, got " + stringForErrorMessage(value),
        path: ""
      };
    }

    for (var i = 0, length = value.length; i < length; i++) {
      var result = testSubtree(value[i], pattern[0]);
      if (result) {
        result.path = _prependPath(i, result.path);
        return result;
      }
    }
    return false;
  }

  // Arbitrary validation checks. The condition can return false or throw a
  // Match.Error (ie, it can internally use check()) to fail.
  if (pattern instanceof Where) {
    var result;
    try {
      result = pattern.condition(value);
    } catch (err) {
      if (!(err instanceof Match.Error))
        throw err;
      return {
        message: err.message,
        path: err.path
      };
    }
    if (result)
      return false;
    // XXX this error is terrible
    return {
      message: "Failed Match.Where validation",
      path: ""
    };
  }

  if (pattern instanceof Maybe) {
    pattern = Match.OneOf(undefined, null, pattern.pattern);
  }
  else if (pattern instanceof Optional) {
    pattern = Match.OneOf(undefined, pattern.pattern);
  }

  if (pattern instanceof OneOf) {
    for (var i = 0; i < pattern.choices.length; ++i) {
      var result = testSubtree(value, pattern.choices[i]);
      if (!result) {
        // No error? Yay, return.
        return false;
      }
      // Match errors just mean try another choice.
    }
    // XXX this error is terrible
    return {
      message: "Failed Match.OneOf, Match.Maybe or Match.Optional validation",
      path: ""
    };
  }

  // A function that isn't something we special-case is assumed to be a
  // constructor.
  if (pattern instanceof Function) {
    if (value instanceof pattern)
      return false;
    return {
      message: "Expected " + (pattern.name ||"particular constructor"),
      path: ""
    };
  }

  var unknownKeysAllowed = false;
  var unknownKeyPattern;
  if (pattern instanceof ObjectIncluding) {
    unknownKeysAllowed = true;
    pattern = pattern.pattern;
  }
  if (pattern instanceof ObjectWithValues) {
    unknownKeysAllowed = true;
    unknownKeyPattern = [pattern.pattern];
    pattern = {};  // no required keys
  }

  if (typeof pattern !== "object") {
    return {
      message: "Bad pattern: unknown pattern type",
      path: ""
    };
  }

  // An object, with required and optional keys. Note that this does NOT do
  // structural matches against objects of special types that happen to match
  // the pattern: this really needs to be a plain old {Object}!
  if (typeof value !== 'object') {
    return {
      message: "Expected object, got " + typeof value,
      path: ""
    };
  }
  if (value === null) {
    return {
      message: "Expected object, got null",
      path: ""
    };
  }
  if (! isPlainObject(value)) {
    return {
      message: "Expected plain object",
      path: ""
    };
  }

  var requiredPatterns = {};
  var optionalPatterns = {};
  _.each(pattern, function (subPattern, key) {
    if (subPattern instanceof Optional || subPattern instanceof Maybe)
      optionalPatterns[key] = subPattern.pattern;
    else
      requiredPatterns[key] = subPattern;
  });

  //XXX: replace with underscore's _.allKeys if Meteor updates underscore to 1.8+ (or lodash)
  function allKeys(obj) {
    var keys = [];
    if (obj) {
      for (var key in obj) keys.push(key);
    }
    return keys;
  }

  for (var keys = allKeys(value), i = 0, length = keys.length; i < length; i++) {
    var key = keys[i];
    var subValue = value[key];
    if (Object.prototype.hasOwnProperty.call(requiredPatterns, key)) {
      var result = testSubtree(subValue, requiredPatterns[key]);
      if (result) {
        result.path = _prependPath(key, result.path);
        return result;
      }
      delete requiredPatterns[key];
    } else if (Object.prototype.hasOwnProperty.call(optionalPatterns, key)) {
      var result = testSubtree(subValue, optionalPatterns[key]);
      if (result) {
        result.path = _prependPath(key, result.path);
        return result;
      }
    } else {
      if (!unknownKeysAllowed) {
        return {
          message: "Unknown key",
          path: key
        };
      }
      if (unknownKeyPattern) {
        var result = testSubtree(subValue, unknownKeyPattern[0]);
        if (result) {
          result.path = _prependPath(key, result.path);
          return result;
        }
      }
    }
  }

  var keys = Object.keys(requiredPatterns);
  if (keys.length) {
    return {
      message: "Missing key '" + keys[0] + "'",
      path: ""
    };
  }
};

class ArgumentChecker {
  constructor (args, description) {
    var self = this;
    // Make a SHALLOW copy of the arguments. (We'll be doing identity checks
    // against its contents.)
    self.args = [...args];
    // Since the common case will be to check arguments in order, and we splice
    // out arguments when we check them, make it so we splice out from the end
    // rather than the beginning.
    self.args.reverse();
    self.description = description;
  }

  checking(value) {
    var self = this;
    if (self._checkingOneValue(value))
      return;
    // Allow check(arguments, [String]) or check(arguments.slice(1), [String])
    // or check([foo, bar], [String]) to count... but only if value wasn't
    // itself an argument.
    if (Array.isArray(value) || isArguments(value)) {
      Array.prototype.forEach.call(value, self._checkingOneValue.bind(self));
    }
  }

  _checkingOneValue(value) {
    var self = this;
    for (var i = 0; i < self.args.length; ++i) {
      // Is this value one of the arguments? (This can have a false positive if
      // the argument is an interned primitive, but it's still a good enough
      // check.)
      // (NaN is not === to itself, so we have to check specially.)
      if (value === self.args[i] ||
          (Number.isNaN(value) && Number.isNaN(self.args[i]))) {
        self.args.splice(i, 1);
        return true;
      }
    }
    return false;
  }

  throwUnlessAllArgumentsHaveBeenChecked() {
    var self = this;
    if (self.args.length > 0)
      throw new Error("Did not check() all arguments during " +
                      self.description);
  }
}

var _jsKeywords = ["do", "if", "in", "for", "let", "new", "try", "var", "case",
  "else", "enum", "eval", "false", "null", "this", "true", "void", "with",
  "break", "catch", "class", "const", "super", "throw", "while", "yield",
  "delete", "export", "import", "public", "return", "static", "switch",
  "typeof", "default", "extends", "finally", "package", "private", "continue",
  "debugger", "function", "arguments", "interface", "protected", "implements",
  "instanceof"];

// Assumes the base of path is already escaped properly
// returns key + base
var _prependPath = function (key, base) {
  if ((typeof key) === "number" || key.match(/^[0-9]+$/))
    key = "[" + key + "]";
  else if (!key.match(/^[a-z_$][0-9a-z_$]*$/i) || _jsKeywords.includes(key))
    key = JSON.stringify([key]);

  if (base && base[0] !== "[")
    return key + '.' + base;
  return key + base;
};

function isArguments(item) {
  return Object.prototype.toString.call(item) === '[object Arguments]';
}
<|endoftext|>