<commit_before>// Get all the "··· More" menus
var menus = getMenusArray();

addQuoteButton(menus);
addMutationListener();

/**
 ** FUNCTIONS
 **/

function addMutationListener() {
    $('#stream-items-id').arrive('li[data-item-type=tweet]', function() {
        addQuoteButton($(this).find('li.more-tweet-actions .dropdown-menu > ul').toArray());
    });
}

function addQuoteButton(menus) {
    for (var index in menus) {
        var newItem = $(document.createElement('li'));
        var innerButton = $(document.createElement('button'));
        var menu = $(menus[index]);
        var quoteButtonText = chrome.i18n.getMessage("quote");

        // Set up the button
        innerButton.attr('class', 'dropdown-link');
        innerButton.attr('role', 'menuitem');
        innerButton.attr('type', 'button');
        // Attach it the click listener
        innerButton.click(onClickQuoteButton);
        // Set the button's text
        innerButton.text(quoteButtonText);
        // Add the "Quote" button
        newItem.prepend(innerButton);
        // Add the item to the list
        menu.prepend(newItem);
    }
}

function getMenusArray() {
    return [].filter.call(
                document.querySelectorAll("li.more-tweet-actions .dropdown-menu > ul"),
                function(value, index, array) {
                    return true;
                },
                []);
}

function getTweetAuthor(element) {
    return $(element).closest('.tweet').find('.username').text();
}

function getTweetText(element) {
    return $(element).closest('.tweet').find('.tweet-text').text();
}

function onClickQuoteButton(event) {
    // Get the btn's parent tweet's author
    var author = getTweetAuthor(event.currentTarget);
    // Get the btn's parent tweet's text
    var text = getTweetText(event.currentTarget);
    // Set the focus on the text field first (so the tweet form will be expanded)
    $('#tweet-box-mini-home-profile').focus();
    // Set the tweet text as its value
    $('#tweet-box-mini-home-profile').first().text('RT ' + author + ': ' + text);
    // Set the scroll bar at the top of the page
    $(document).scrollTop(0);
    // Close the dropdown menu
    $(event.currentTarget).closest('.tweet').find('.dropdown-toggle').click();
}
<commit_msg>timeline> Renamed 'addQuoteButton()' in 'addQuoteButtonS()'<commit_after>// Get all the "··· More" menus
var menus = getMenusArray();

addQuoteButtons(menus);
addMutationListener();

/**
 ** FUNCTIONS
 **/

function addMutationListener() {
    $('#stream-items-id').arrive('li[data-item-type=tweet]', function() {
        addQuoteButtons($(this).find('li.more-tweet-actions .dropdown-menu > ul').toArray());
    });
}

function addQuoteButtons(menus) {
    for (var index in menus) {
        var newItem = $(document.createElement('li'));
        var innerButton = $(document.createElement('button'));
        var menu = $(menus[index]);
        var quoteButtonText = chrome.i18n.getMessage("quote");

        // Set up the button
        innerButton.attr('class', 'dropdown-link');
        innerButton.attr('role', 'menuitem');
        innerButton.attr('type', 'button');
        // Attach it the click listener
        innerButton.click(onClickQuoteButton);
        // Set the button's text
        innerButton.text(quoteButtonText);
        // Add the "Quote" button
        newItem.prepend(innerButton);
        // Add the item to the list
        menu.prepend(newItem);
    }
}

function getMenusArray() {
    return [].filter.call(
                document.querySelectorAll("li.more-tweet-actions .dropdown-menu > ul"),
                function(value, index, array) {
                    return true;
                },
                []);
}

function getTweetAuthor(element) {
    return $(element).closest('.tweet').find('.username').text();
}

function getTweetText(element) {
    return $(element).closest('.tweet').find('.tweet-text').text();
}

function onClickQuoteButton(event) {
    // Get the btn's parent tweet's author
    var author = getTweetAuthor(event.currentTarget);
    // Get the btn's parent tweet's text
    var text = getTweetText(event.currentTarget);
    // Set the focus on the text field first (so the tweet form will be expanded)
    $('#tweet-box-mini-home-profile').focus();
    // Set the tweet text as its value
    $('#tweet-box-mini-home-profile').first().text('RT ' + author + ': ' + text);
    // Set the scroll bar at the top of the page
    $(document).scrollTop(0);
    // Close the dropdown menu
    $(event.currentTarget).closest('.tweet').find('.dropdown-toggle').click();
}
<|endoftext|>


<commit_before>// watch the graph for changes

const nodes = (graphNodes, cy) => {
  const titleBar = document.getElementById('title-bar-id')

  if (graphNodes.same(cy.nodes()) === false) {
    // add the files location to the title bar
    titleBar.innerHTML += `<span style='color: var(--blue-color);'> •</span>`
  } else {
    titleBar.innerHTML = titleBar.innerHTML.replace(' •', ' ')
  }
}

const edges = (graphEdges, cy) => {
  console.log(graphEdges.same(cy.edges()))

  const titleBar = document.getElementById('title-bar-id')

  if (
    graphEdges.same(cy.edges()) === false &&
    titleBar.innerText !== `${titleBar.innerHTML} *`
  ) {
    // add the files location to the title bar
    titleBar.innerHTML += ` *`
  } else if (graphEdges.same(cy.edges()) === false) {
    titleBar.innerHTML = titleBar.innerHTML
  } else {
    titleBar.innerHTML = titleBar.innerHTML.replace(' *', ' ')
  }
}

module.exports = {
  nodes,
  edges
}
<commit_msg>:bug: watcher would render more than one tokens<commit_after>// watch the graph for changes

let changeToken = false

const nodes = (graphNodes, cy) => {
  const titleBar = document.getElementById('title-bar-id')

  if (graphNodes.same(cy.nodes()) === false && changeToken === false) {
    titleBar.innerHTML += `<span style='color: var(--blue-color);'> •</span>`
    changeToken = true
  } else if (graphNodes.same(cy.nodes()) === true) {
    titleBar.innerHTML = titleBar.innerHTML.replace(' •', ' ')
    changeToken = false
  }
}

const edges = (graphEdges, cy) => {
  console.log(graphEdges.same(cy.edges()))

  const titleBar = document.getElementById('title-bar-id')

  if (
    graphEdges.same(cy.edges()) === false &&
    titleBar.innerText !== `${titleBar.innerHTML} *`
  ) {
    // add the files location to the title bar
    titleBar.innerHTML += ` *`
  } else if (graphEdges.same(cy.edges()) === false) {
    titleBar.innerHTML = titleBar.innerHTML
  } else {
    titleBar.innerHTML = titleBar.innerHTML.replace(' *', ' ')
  }
}

module.exports = {
  nodes,
  edges
}
<|endoftext|>


<commit_before>/**
 * Very simple in-browser unit-test library, with zero deps.
 *
 * Background turns green if all tests pass, otherwise red.
 * View the JavaScript console to see failure reasons.
 *
 * Example:
 *
 *   adder.js (code under test)
 *
 *     function add(a, b) {
 *       return a + b;
 *     }
 *
 *   adder-test.html (tests - just open a browser to see results)
 *
 *     <script src="tinytest.js"></script>
 *     <script src="adder.js"></script>
 *     <script>
 *
 *     tests({
 *
 *       'adds numbers': function() {
 *         assertEquals(6, add(2, 4));
 *         assertEquals(6.6, add(2.6, 4));
 *       },
 *
 *       'subtracts numbers': function() {
 *         assertEquals(-2, add(2, -4));
 *       },
 *
 *     });
 *     </script>
 *
 * That's it. Stop using over complicated frameworks that get in your way.
 *
 * -Joe Walnes
 */
var TinyTest = {

    run: function(tests) {
        var failures = 0;
        for (var testName in tests) {
            var testAction = tests[testName];
            try {
                testAction.apply(this);
                console.log('Test:', testName, 'OK');
            } catch (e) {
                failures++;
                console.error('Test:', testName, 'FAILED');
                console.error(e.stack);
            }
        }
        setTimeout(function() { // Give document a chance to complete
            if (window.document && document.body) {
                document.body.style.backgroundColor = (failures == 0 ? '#99ff99' : '#ff9999');
            }
        }, 0);
    },

    fail: function(msg) {
        throw new Error('fail(): ' + msg);
    },

    assert: function(value, msg) {
        if (!value) {
            throw new Error('assert(): ' + msg);
        }
    },

    assertEquals: function(expected, actual) {
        if (expected != actual) {
            throw new Error('assertEquals() "' + expected + '" != "' + actual + '"');
        }
    },

    assertStrictEquals: function(expected, actual) {
        if (expected !== actual) {
            throw new Error('assertStrictEquals() "' + expected + '" !== "' + actual + '"');
        }
    },

};

var fail               = TinyTest.fail.bind(TinyTest);
var assert             = TinyTest.assert.bind(TinyTest);
var assertEquals       = TinyTest.assertEquals.bind(TinyTest);
var assertStrictEquals = TinyTest.assertStrictEquals.bind(TinyTest);
var tests              = TinyTest.run.bind(TinyTest);
<commit_msg>license ref in header<commit_after>/**
 * Very simple in-browser unit-test library, with zero deps.
 *
 * Background turns green if all tests pass, otherwise red.
 * View the JavaScript console to see failure reasons.
 *
 * Example:
 *
 *   adder.js (code under test)
 *
 *     function add(a, b) {
 *       return a + b;
 *     }
 *
 *   adder-test.html (tests - just open a browser to see results)
 *
 *     <script src="tinytest.js"></script>
 *     <script src="adder.js"></script>
 *     <script>
 *
 *     tests({
 *
 *       'adds numbers': function() {
 *         assertEquals(6, add(2, 4));
 *         assertEquals(6.6, add(2.6, 4));
 *       },
 *
 *       'subtracts numbers': function() {
 *         assertEquals(-2, add(2, -4));
 *       },
 *
 *     });
 *     </script>
 *
 * That's it. Stop using over complicated frameworks that get in your way.
 *
 * -Joe Walnes
 * MIT License. See https://github.com/joewalnes/jstinytest/
 */
var TinyTest = {

    run: function(tests) {
        var failures = 0;
        for (var testName in tests) {
            var testAction = tests[testName];
            try {
                testAction.apply(this);
                console.log('Test:', testName, 'OK');
            } catch (e) {
                failures++;
                console.error('Test:', testName, 'FAILED');
                console.error(e.stack);
            }
        }
        setTimeout(function() { // Give document a chance to complete
            if (window.document && document.body) {
                document.body.style.backgroundColor = (failures == 0 ? '#99ff99' : '#ff9999');
            }
        }, 0);
    },

    fail: function(msg) {
        throw new Error('fail(): ' + msg);
    },

    assert: function(value, msg) {
        if (!value) {
            throw new Error('assert(): ' + msg);
        }
    },

    assertEquals: function(expected, actual) {
        if (expected != actual) {
            throw new Error('assertEquals() "' + expected + '" != "' + actual + '"');
        }
    },

    assertStrictEquals: function(expected, actual) {
        if (expected !== actual) {
            throw new Error('assertStrictEquals() "' + expected + '" !== "' + actual + '"');
        }
    },

};

var fail               = TinyTest.fail.bind(TinyTest);
var assert             = TinyTest.assert.bind(TinyTest);
var assertEquals       = TinyTest.assertEquals.bind(TinyTest);
var assertStrictEquals = TinyTest.assertStrictEquals.bind(TinyTest);
var tests              = TinyTest.run.bind(TinyTest);
<|endoftext|>


<commit_before>/* globals define */

/**
 * BoardView is basically a grid layout with a bunch of surfaces
 */
define(function(require, exports, module) {
  var View          = require('famous/core/View');
  var Surface       = require('famous/core/Surface');
  var Transform     = require('famous/core/Transform');
  var StateModifier = require('famous/modifiers/StateModifier');
  var GridLayout    = require('famous/views/GridLayout');

  var boardDimensions = [5,7];

  function _createBackground() {
    var bgSurface = new Surface({
      properties: {
        backgroundColor: 'black'
      }
    });

    this.add(bgSurface);
  }

  function _createSurfaces() {
    var size = boardDimensions[0] * boardDimensions[1];
    var surfaces = [];
    for(var i = 0; i < size; i++){
      surfaces.push(new Surface({
        content: 'panel ' + (i + 1),
        size: undefined,
        properties: {
          color: 'black',
          backgroundColor: 'hsl(100, 100%, 100%)',
          textAlign: 'center'
        }

      }));
    }

    return surfaces;
  }

  function _createGrid(dimensions) {
    var grid = new GridLayout({
      dimensions: dimensions
    });

    var surfaces = _createSurfaces();
    grid.sequenceFrom(surfaces);
    this.add(grid);

  }

  function BoardView() {
    View.apply(this, arguments);

    // add background
    _createBackground.call(this);
    // add grid
    _createGrid.call(this, boardDimensions);
  }

  BoardView.prototype = Object.create(View.prototype);
  BoardView.prototype.constructor = BoardView;

  BoardView.DEFAULT_OPTIONS = {};

  module.exports = BoardView;
});
<commit_msg>returning x coordinates<commit_after>/* globals define */

/**
 * BoardView is basically a grid layout with a bunch of surfaces
 */
define(function(require, exports, module) {
  var View          = require('famous/core/View');
  var Surface       = require('famous/core/Surface');
  var Transform     = require('famous/core/Transform');
  var StateModifier = require('famous/modifiers/StateModifier');
  var GridLayout    = require('famous/views/GridLayout');

  var boardDimensions = [5,7];

  function _createBackground() {
    var bgSurface = new Surface({
      properties: {
        backgroundColor: 'black'
      }
    });

    this.add(bgSurface);
  }

  function _createSurfaces() {
    var size = boardDimensions[0] * boardDimensions[1];
    var surfaces = [];
    var x = 0;
    var y = 0;

    function getX(index){
      var columns = boardDimensions[0];
      var rows = boardDimensions[1];
      var x = index;
      for(var row = 0; row < rows; row++){ // x = 5, row = 1, columns = 5, rows = 7
        if(x < columns){
          console.log(row);
          return row;
        }else if( (x >= (columns * row)) && x < ( columns * (row + 1) )){
          console.log(row);
          return row;
        }

      }

    }

    for(var i = 0; i < size; i++){

      surfaces.push(new Surface({
        content: 'x: ' + getX(i),
        size: undefined,
        properties: {
          color: 'black',
          backgroundColor: 'hsl(100, 100%, 100%)',
          textAlign: 'center'
        }

      }));
    }

    return surfaces;
  }

  function _createGrid(dimensions) {
    var grid = new GridLayout({
      dimensions: dimensions
    });

    var surfaces = _createSurfaces();
    grid.sequenceFrom(surfaces);
    this.add(grid);

  }

  function BoardView() {
    View.apply(this, arguments);

    // add background
    _createBackground.call(this);
    // add grid
    _createGrid.call(this, boardDimensions);
  }

  BoardView.prototype = Object.create(View.prototype);
  BoardView.prototype.constructor = BoardView;

  BoardView.DEFAULT_OPTIONS = {};

  module.exports = BoardView;
});
<|endoftext|>


<commit_before>exports.orgs = ['mozilla-appmaker', 'mozilla', 'mozilla-b2g', 'mozilla-services'];
//exports.orgs = ['mozilla-b2g'];
<commit_msg>add mozillafordevelopment org<commit_after>exports.orgs = ['mozilla-appmaker', 'mozilla', 'mozilla-b2g', 'mozilla-services', 'mozillafordevelopment'];
//exports.orgs = ['mozilla-b2g'];
<|endoftext|>


<commit_before>var express = require('express'),
    app = express(),
    sdk = require('./grasshopper-sdk'),
    internalSdk = new sdk(require((process.argv.length == 3 && process.argv[2] == 'test') ? './config/configuration.test' : './config/configuration')),
    grasshopper = null,
    LOGGING_CATEGORY = "EXPRESS_SERVICE",
    api = {},
    PORT = process.env.PORT || 3000;

app.configure( function () {
    app.use(function(req, res, next) {
        res.setHeader("Access-Control-Allow-Origin", "*");
        res.setHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
        res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
        return next();
    });
    app.use(express.logger('dev'));
    app.use(express.bodyParser());
    app.use(express.methodOverride());
});

internalSdk.on('ready', function(val){
    grasshopper      = val;
    api.token        = require('./api/token')(grasshopper);
    api.users        = require('./api/users')(grasshopper);
    api.contentTypes = require('./api/contentTypes')(grasshopper);
    api.nodes        = require('./api/nodes')(grasshopper);

    app.get('/token', api.token.get);

    app.get('/node/:id', api.nodes.getById);
    app.post('/node', api.nodes.create);
    app.post('/node/:id/contenttype', api.nodes.addContentTypes);
    app.post('/nodes', api.nodes.create);

    app.get('/contenttypes', api.contentTypes.getList);
    app.get('/contenttypes/:id', api.contentTypes.getById);
    app.delete('/contenttypes/:id', api.contentTypes.deleteById);
    app.put('/contenttypes', api.contentTypes.update);
    app.post('/contenttypes', api.contentTypes.create);

    app.get('/users/:id', api.users.getById);
    app.delete('/users/:id', api.users.deleteById);
    app.delete('/users/:id/permissions/:nodeid', api.users.deletePermission);

    app.put('/users', api.users.update);
    app.put('/user', api.users.update);
    app.put('/users/:id', api.users.update);
    app.post('/users/:id/permissions', api.users.updatePermission);

    app.get('/users', api.users.getList);
    app.get('/user', api.users.getCurrentUser);

    app.post('/users', api.users.create);

    grasshopper.log.info(LOGGING_CATEGORY, "GRASSHOPPER SDK LOADED!!!!!!!!!!");
});

internalSdk.on('failed', function(err){
    console.log("Could not load grasshopper.");
    console.log(err);
});

app.listen(PORT);
console.log('Listening on port ' + PORT + '...');
<commit_msg>Added route for slugs<commit_after>var express = require('express'),
    app = express(),
    sdk = require('./grasshopper-sdk'),
    internalSdk = new sdk(require((process.argv.length == 3 && process.argv[2] == 'test') ? './config/configuration.test' : './config/configuration')),
    grasshopper = null,
    LOGGING_CATEGORY = "EXPRESS_SERVICE",
    api = {},
    PORT = process.env.PORT || 3000;

app.configure( function () {
    app.use(function(req, res, next) {
        res.setHeader("Access-Control-Allow-Origin", "*");
        res.setHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
        res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
        return next();
    });
    app.use(express.logger('dev'));
    app.use(express.bodyParser());
    app.use(express.methodOverride());
});

internalSdk.on('ready', function(val){
    grasshopper      = val;
    api.token        = require('./api/token')(grasshopper);
    api.users        = require('./api/users')(grasshopper);
    api.contentTypes = require('./api/contentTypes')(grasshopper);
    api.nodes        = require('./api/nodes')(grasshopper);

    app.get('/token', api.token.get);

    app.get('/node/:id*', api.nodes.getById);
    app.post('/node', api.nodes.create);
    app.post('/node/:id/contenttype', api.nodes.addContentTypes);
    app.post('/nodes', api.nodes.create);

    app.get('/contenttypes', api.contentTypes.getList);
    app.get('/contenttypes/:id', api.contentTypes.getById);
    app.delete('/contenttypes/:id', api.contentTypes.deleteById);
    app.put('/contenttypes', api.contentTypes.update);
    app.post('/contenttypes', api.contentTypes.create);

    app.get('/users/:id', api.users.getById);
    app.delete('/users/:id', api.users.deleteById);
    app.delete('/users/:id/permissions/:nodeid', api.users.deletePermission);

    app.put('/users', api.users.update);
    app.put('/user', api.users.update);
    app.put('/users/:id', api.users.update);
    app.post('/users/:id/permissions', api.users.updatePermission);

    app.get('/users', api.users.getList);
    app.get('/user', api.users.getCurrentUser);

    app.post('/users', api.users.create);

    grasshopper.log.info(LOGGING_CATEGORY, "GRASSHOPPER SDK LOADED!!!!!!!!!!");
});

internalSdk.on('failed', function(err){
    console.log("Could not load grasshopper.");
    console.log(err);
});

app.listen(PORT);
console.log('Listening on port ' + PORT + '...');
<|endoftext|>


<commit_before>// BEGIN(BROWSER)

/*jshint eqnull:true*/
this.Handlebars = {};

(function() {

Handlebars.VERSION = "1.0.beta.5";

Handlebars.helpers  = {};
Handlebars.partials = {};

Handlebars.registerHelper = function(name, fn, inverse) {
  if(inverse) { fn.not = inverse; }
  this.helpers[name] = fn;
};

Handlebars.registerPartial = function(name, str) {
  this.partials[name] = str;
};

Handlebars.registerHelper('helperMissing', function(arg) {
  if(arguments.length === 2) {
    return undefined;
  } else {
    throw new Error("Could not find property '" + arg + "'");
  }
});

var toString = Object.prototype.toString, functionType = "[object Function]";

Handlebars.registerHelper('blockHelperMissing', function(context, options) {
  var inverse = options.inverse || function() {}, fn = options.fn;

  var ret = "";
  var type = toString.call(context);

  if(type === functionType) { context = context.call(this); }

  if(context === true) {
    return fn(this);
  } else if(context === false || context == null) {
    return inverse(this);
  } else if(type === "[object Array]") {
    if(context.length > 0) {
      for(var i=0, j=context.length; i<j; i++) {
        ret = ret + fn(context[i]);
      }
    } else {
      ret = inverse(this);
    }
    return ret;
  } else {
    return fn(context);
  }
});

Handlebars.registerHelper('each', function(context, options) {
  var fn = options.fn, inverse = options.inverse;
  var ret = "";

  if(context && context.length > 0) {
    for(var i=0, j=context.length; i<j; i++) {
      ret = ret + fn(context[i]);
    }
  } else {
    ret = inverse(this);
  }
  return ret;
});

Handlebars.registerHelper('if', function(context, options) {
  var type = toString.call(context);
  if(type === functionType) { context = context.call(this); }

  if(!context || Handlebars.Utils.isEmpty(context)) {
    return options.inverse(this);
  } else {
    return options.fn(this);
  }
});

Handlebars.registerHelper('unless', function(context, options) {
  var fn = options.fn, inverse = options.inverse;
  options.fn = inverse;
  options.inverse = fn;

  return Handlebars.helpers['if'].call(this, context, options);
});

Handlebars.registerHelper('with', function(context, options) {
  return options.fn(context);
});

Handlebars.registerHelper('log', function(context) {
  Handlebars.log(context);
});

}());

// END(BROWSER)

module.exports = Handlebars;

<commit_msg>Add support for @index in #each<commit_after>// BEGIN(BROWSER)

/*jshint eqnull:true*/
this.Handlebars = {};

(function() {

Handlebars.VERSION = "1.0.beta.5";

Handlebars.helpers  = {};
Handlebars.partials = {};

Handlebars.registerHelper = function(name, fn, inverse) {
  if(inverse) { fn.not = inverse; }
  this.helpers[name] = fn;
};

Handlebars.registerPartial = function(name, str) {
  this.partials[name] = str;
};

Handlebars.registerHelper('helperMissing', function(arg) {
  if(arguments.length === 2) {
    return undefined;
  } else {
    throw new Error("Could not find property '" + arg + "'");
  }
});

var toString = Object.prototype.toString, functionType = "[object Function]";

Handlebars.registerHelper('blockHelperMissing', function(context, options) {
  var inverse = options.inverse || function() {}, fn = options.fn;

  var ret = "";
  var type = toString.call(context);

  if(type === functionType) { context = context.call(this); }

  if(context === true) {
    return fn(this);
  } else if(context === false || context == null) {
    return inverse(this);
  } else if(type === "[object Array]") {
    if(context.length > 0) {
      for(var i=0, j=context.length; i<j; i++) {
        ret = ret + fn(context[i]);
      }
    } else {
      ret = inverse(this);
    }
    return ret;
  } else {
    return fn(context);
  }
});

Handlebars.K = function() {};

Handlebars.createFrame = Object.create || function(object) {
  Handlebars.K.prototype = object;
  var obj = new Handlebars.K();
  Handlebars.K.prototype = null;
  return obj;
};

Handlebars.registerHelper('each', function(context, options) {
  var fn = options.fn, inverse = options.inverse;
  var ret = "", data;

  if (options.data) {
    data = Handlebars.createFrame(options.data);
  }

  if(context && context.length > 0) {
    for(var i=0, j=context.length; i<j; i++) {
      if (data) { data.index = i; }
      ret = ret + fn(context[i], { data: data });
    }
  } else {
    ret = inverse(this);
  }
  return ret;
});

Handlebars.registerHelper('if', function(context, options) {
  var type = toString.call(context);
  if(type === functionType) { context = context.call(this); }

  if(!context || Handlebars.Utils.isEmpty(context)) {
    return options.inverse(this);
  } else {
    return options.fn(this);
  }
});

Handlebars.registerHelper('unless', function(context, options) {
  var fn = options.fn, inverse = options.inverse;
  options.fn = inverse;
  options.inverse = fn;

  return Handlebars.helpers['if'].call(this, context, options);
});

Handlebars.registerHelper('with', function(context, options) {
  return options.fn(context);
});

Handlebars.registerHelper('log', function(context) {
  Handlebars.log(context);
});

}());

// END(BROWSER)

module.exports = Handlebars;

<|endoftext|>


<commit_before>var moment = require('moment');
var googleapis = require('googleapis');
var GoogleToken = require('gapitoken');
var OAuth2Client = googleapis.OAuth2Client;

var oauth2Client;

var curApp;

exports.init = function(app) {
	curApp = app;

	app.express.get('/hangout-farming', function(req, res){
		var url = "http://" + app.options.HOST + ":" + app.options.PORT + "/hangout-callback";

		oauth2Client =
		new OAuth2Client(app.options.GOOGLE_CLIENT_ID, app.options.GOOGLE_CLIENT_SECRET, url);

		var url = oauth2Client.generateAuthUrl({
			access_type: 'offline',
			scope: 'https://www.googleapis.com/auth/calendar'
		});

		res.redirect(url);
	});

	app.express.get('/hangout-callback', function(req, res) {
		// console.log(req.query["code"]);
		oauth2Client.getToken(req.query["code"], function(err, token) {
			if(err) {
				res.send(500, "Error getting token.");
				return;
			}

			oauth2Client.credentials = token;
			// okay, we've got a token now.	lets actually issue a request.
			googleapis.discover('calendar', 'v3').execute(function(err, client) {
				var now = moment().format();

				client.calendar
				.events
				.insert({
					calendarId: 'primary',
					resource: {
						summary: 'hangout',
						description: 'hangout',
						reminders: {
							overrides: {
								method: 'popup',
								minutes: 0
							}
						},
						start: {
							dateTime: now
						},
						end: {
							dateTime: now
						},
						attendees: []
						}
					})
					.withAuthClient(oauth2Client)
					.execute(function(err, event) {
						if(!err && "hangoutLink" in event) {
							// push it into redis for safekeeping.
							app.redis.lpush("global:hangout_urls", event.hangoutLink, function(err, num) {
								app.logger.info("logged new hangout url: " + event.hangoutLink + "; total: " + num);
								// purely for convenience of clicking to farm another url.
								res.send("urls available: " + num + "
CLICK ME");

								// now delete the event so it doesn't clutter up the user's calendar.
								// as far as I can tell,
								client.calendar.events.delete({
									calendarId:'primary',
									eventId:event.id
									}).withAuthClient(oauth2Client)
									.execute();
							});
						} else {
							res.send(err + "
" + "your account doesn't seem to have the 'create video calls for events I create' option enabled. go to google calendar, settings (in the upper right hand corner) and enable that option. Then CLICK HERE!");
						}
					});
				});

		});
	});

	app.logger.info("registered hangout-faming urls");
}

exports.getNextHangoutUrl = function(callback) {

	// just hit redis directly here, with an rpop.
	curApp.redis.rpop("global:hangout_urls", function(err, url) {
		curApp.logger.debug("returning hangout url: " + url + " (err: " + err + ")");
		callback && callback(err, url);
	});
}

exports.reuseUrl = function(url) {
	curApp.redis.rpush("global:hangout_urls", url, function(err) {
		curApp.logger.debug("re-added a url that was no longer needed: " + url);
	});
}<commit_msg>Stopped deleting the event, changed some properties. Seems to work for most people but not Srishti for some reason. Need to look into this.<commit_after>var moment = require('moment');
var googleapis = require('googleapis');
var GoogleToken = require('gapitoken');
var OAuth2Client = googleapis.OAuth2Client;

var oauth2Client;

var curApp;

exports.init = function(app) {
	curApp = app;

	app.express.get('/hangout-farming', function(req, res){
		var url = "http://" + app.options.HOST + ":" + app.options.PORT + "/hangout-callback";

		oauth2Client =
		new OAuth2Client(app.options.GOOGLE_CLIENT_ID, app.options.GOOGLE_CLIENT_SECRET, url);

		var url = oauth2Client.generateAuthUrl({
			access_type: 'offline',
			scope: 'https://www.googleapis.com/auth/calendar'
		});

		res.redirect(url);
	});

	app.express.get('/hangout-callback', function(req, res) {
		// console.log(req.query["code"]);
		oauth2Client.getToken(req.query["code"], function(err, token) {
			if(err) {
				res.send(500, "Error getting token.");
				return;
			}

			oauth2Client.credentials = token;
			// okay, we've got a token now.	lets actually issue a request.
			googleapis.discover('calendar', 'v3').execute(function(err, client) {
				var now = moment().format();

				client.calendar
				.events
				.insert({
					calendarId: 'primary',
					resource: {
						summary: 'hangout',
						description: 'hangout',
						anyoneCanAddSelf: true,
						visibility: "public",
						transparency: "transparent",
						reminders: {
							overrides: {
								method: 'popup',
								minutes: 0
							}
						},
						start: {
							dateTime: moment().add('days', 1).format()
						},
						end: {
							dateTime: moment().add('days', 30).format()
						},
						attendees: []
						}
					})
					.withAuthClient(oauth2Client)
					.execute(function(err, event) {
						if(!err && "hangoutLink" in event) {
							// push it into redis for safekeeping.
							app.redis.lpush("global:hangout_urls", event.hangoutLink, function(err, num) {
								app.logger.info("logged new hangout url: " + event.hangoutLink + "; total: " + num);
								// purely for convenience of clicking to farm another url.
								res.send("urls available: " + num + "
CLICK ME");

								// now delete the event so it doesn't clutter up the user's calendar.
								// as far as I can tell,
								// client.calendar.events.delete({
								// 	calendarId:'primary',
								// 	eventId:event.id
								// 	}).withAuthClient(oauth2Client)
								// 	.execute();
							});
						} else {
							res.send(err + "
" + "your account doesn't seem to have the 'create video calls for events I create' option enabled. go to google calendar, settings (in the upper right hand corner) and enable that option. Then CLICK HERE!");
						}
					});
				});

		});
	});

	app.logger.info("registered hangout-faming urls");
}

exports.getNextHangoutUrl = function(callback) {

	// just hit redis directly here, with an rpop.
	curApp.redis.rpop("global:hangout_urls", function(err, url) {
		curApp.logger.debug("returning hangout url: " + url + " (err: " + err + ")");
		callback && callback(err, url);
	});
}

exports.reuseUrl = function(url) {
	curApp.redis.rpush("global:hangout_urls", url, function(err) {
		curApp.logger.debug("re-added a url that was no longer needed: " + url);
	});
}<|endoftext|>


<commit_before>
function tokenize(sentence)
{
	var my_words = sentence.split(" ")
	var result = {}
	for (var i = 0; i < my_words.length; i++)
	{

		var my_word = my_words[i]
		if(my_word.length < 1)
			continue
		var w = words[my_word]

		if (w == null){
			konsole.print( my_word + " - wasn't understood")
			return false
		}

		if(w.type == abstract)
		{
			var found = null
			var err = false
			for(var i = 0;  i< inventory.length && err == false; i++)
			{
				if( inventory[i] == 0 )
					continue
				if (inventory[i].is_a && inventory[i].is_a == w)
				{
					if(found != null)
					{
						err = true
						konsole.print("You need to be more specific, I found both "+ get_text(inventory[i])+ " and " + get_text(found) + "!")
						return false
					}
					found = inventory[i]
				}
			}
		for(var i = 0;  i< map[here].objects.length && err == false; i++)
			{
				var o = map[here].objects[i]
				if(o[0].is_a)

				if( o == 0 )
					continue
				if (o[0].is_a && o[0].is_a == w && o[3])
				{
					if(found != null)
					{
						err = true
						konsole.print("You need to be more specific, I found both "+ get_text(o[0]) + " and " + get_text(found) + "!")
						return false
					}
					found = o[0]
				}
			}
			w = found
			if(err)
				w = null
		}

		if (result[w.type] != undefined && w.type != null)
		{
			konsole.print( "I didn't inderstand your sentence, sorry")
			return false
		}
		if ( w.type != null){
		result[w.type] = w
		if (w.eat != null && i + 1 < my_words.length  && w.eat == my_words[i+1])
			{
				i++
			}

	}
	}
	return result

}
function autocomplete(word)
{
	var result = []
	for (var key in words) {
  	if (words.hasOwnProperty(key)) {
  		if (words[key].text.substring(0, word.length) == word) {

  				result [ result.length] = words[key].text

 	 	}
	}
	}
	if(result.length > 1 && result.length < 5)
	{
			konsole.print("Options: " + result)
			result = [false]
	}
	return result[0]
}<commit_msg>Null-check<commit_after>
function tokenize(sentence)
{
	var my_words = sentence.split(" ")
	var result = {}
	for (var i = 0; i < my_words.length; i++)
	{

		var my_word = my_words[i]
		if(my_word.length < 1)
			continue
		var w = words[my_word]

		if (w == null){
			konsole.print( my_word + " - wasn't understood")
			return false
		}

		if(w.type == abstract)
		{
			var found = null
			var err = false
			for(var i = 0;  i< inventory.length && err == false; i++)
			{
				if( inventory[i] == 0 )
					continue
				if (inventory[i].is_a && inventory[i].is_a == w)
				{
					if(found != null)
					{
						err = true
						konsole.print("You need to be more specific, I found both "+ get_text(inventory[i])+ " and " + get_text(found) + "!")
						return false
					}
					found = inventory[i]
				}
			}
		for(var i = 0;  i< map[here].objects.length && err == false; i++)
			{
				var o = map[here].objects[i]
				if(o[0].is_a)

				if( o == 0 )
					continue
				if (o[0].is_a && o[0].is_a == w && o[3])
				{
					if(found != null)
					{
						err = true
						konsole.print("You need to be more specific, I found both "+ get_text(o[0]) + " and " + get_text(found) + "!")
						return false
					}
					found = o[0]
				}
			}
			w = found
			if(err)
				w = null
		}
		if(w== null)
		{
			konsole.print("Word not understood")
			return false
		}

		if (result[w.type] != undefined && w.type != null)
		{
			konsole.print( "I didn't inderstand your sentence, sorry")
			return false
		}
		if ( w.type != null){
		result[w.type] = w
		if (w.eat != null && i + 1 < my_words.length  && w.eat == my_words[i+1])
			{
				i++
			}

	}
	}
	return result

}
function autocomplete(word)
{
	var result = []
	for (var key in words) {
  	if (words.hasOwnProperty(key)) {
  		if (words[key].text.substring(0, word.length) == word) {

  				result [ result.length] = words[key].text

 	 	}
	}
	}
	if(result.length > 1 && result.length < 5)
	{
			konsole.print("Options: " + result)
			result = [false]
	}
	return result[0]
}<|endoftext|>


<commit_before>var glMatrix = require("gl-matrix")
  , vec3 = glMatrix.vec3;

/**
 * Constructs a InteractionBox object.
 *
 * @class InteractionBox
 * @memberof Leap
 * @classdesc
 * The InteractionBox class represents a box-shaped region completely within
 * the field of view of the Leap Motion controller.
 *
 * The interaction box is an axis-aligned rectangular prism and provides
 * normalized coordinates for hands, fingers, and tools within this box.
 * The InteractionBox class can make it easier to map positions in the
 * Leap Motion coordinate system to 2D or 3D coordinate systems used
 * for application drawing.
 *
 * The InteractionBox region is defined by a center and dimensions along the x, y, and z axes.
 */
var InteractionBox = exports.InteractionBox = function(data) {
  /**
   * Indicates whether this is a valid InteractionBox object.
   *
   * @member valid
   * @type {Boolean}
   * @memberof Leap.InteractionBox.prototype
   */
  this.valid = true;
  /**
   * The center of the InteractionBox in device coordinates (millimeters).
   * This point is equidistant from all sides of the box.
   *
   * @member center
   * @type {number[]}
   * @memberof Leap.InteractionBox.prototype
   */
  this.center = data.center;

  this.size = data.size;
  /**
   * The width of the InteractionBox in millimeters, measured along the x-axis.
   *
   * @member width
   * @type {number}
   * @memberof Leap.InteractionBox.prototype
   */
  this.width = data.size[0];
  /**
   * The height of the InteractionBox in millimeters, measured along the y-axis.
   *
   * @member height
   * @type {number}
   * @memberof Leap.InteractionBox.prototype
   */
  this.height = data.size[1];
  /**
   * The depth of the InteractionBox in millimeters, measured along the z-axis.
   *
   * @member depth
   * @type {number}
   * @memberof Leap.InteractionBox.prototype
   */
  this.depth = data.size[2];
}

/**
 * Converts a position defined by normalized InteractionBox coordinates
 * into device coordinates in millimeters.
 *
 * This function performs the inverse of normalizePoint().
 *
 * @method denormalizePoint
 * @memberof Leap.InteractionBox.prototype
 * @param {Leap.Vector} normalizedPosition The input position in InteractionBox coordinates.
 * @returns {Leap.Vector} The corresponding denormalized position in device coordinates.
 */
InteractionBox.prototype.denormalizePoint = function(normalizedPosition) {
  return vec3.fromValues(
    ((normalizedPosition[0] + this.center[0]) - 0.5) * this.width,
    ((normalizedPosition[1] + this.center[1]) - 0.5) * this.height,
    ((normalizedPosition[2] + this.center[2]) - 0.5) * this.depth
  );
}

/**
 * Normalizes the coordinates of a point using the interaction box.
 *
 * Coordinates from the Leap Motion frame of reference (millimeters) are
 * converted to a range of [0..1] such that the minimum value of the
 * InteractionBox maps to 0 and the maximum value of the InteractionBox maps to 1.
 *
 * @method normalizePoint
 * @memberof Leap.InteractionBox.prototype
 * @param {Leap.Vector} position The input position in device coordinates.
 * @param {Boolean} clamp Whether or not to limit the output value to the range [0,1]
 * when the input position is outside the InteractionBox. Defaults to true.
 * @returns {Leap.Vector} The normalized position.
 */
InteractionBox.prototype.normalizePoint = function(position, clamp) {
  var vec = vec3.fromValues(
    ((position[0] - this.center[0]) / this.width) + 0.5,
    ((position[1] - this.center[1]) / this.height) + 0.5,
    ((position[2] - this.center[2]) / this.depth) + 0.5
  );

  if (clamp) {
    vec[0] = Math.min(Math.max(vec[0], 0), 1);
    vec[1] = Math.min(Math.max(vec[1], 0), 1);
    vec[2] = Math.min(Math.max(vec[2], 0), 1);
  }
  return vec;
}

/**
 * Writes a brief, human readable description of the InteractionBox object.
 *
 * @method toString
 * @memberof Leap.InteractionBox.prototype
 * @returns {String} A description of the InteractionBox object as a string.
 */
InteractionBox.prototype.toString = function() {
  return "InteractionBox [ width:" + this.width + " | height:" + this.height + " | depth:" + this.depth + " ]";
}

/**
 * An invalid InteractionBox object.
 *
 * You can use this InteractionBox instance in comparisons testing
 * whether a given InteractionBox instance is valid or invalid. (You can also use the
 * InteractionBox.valid property.)
 *
 * @static
 * @type {Leap.InteractionBox}
 * @name Invalid
 * @memberof Leap.InteractionBox
 */
InteractionBox.Invalid = { valid: false };
<commit_msg>with working math<commit_after>var glMatrix = require("gl-matrix")
  , vec3 = glMatrix.vec3;

/**
 * Constructs a InteractionBox object.
 *
 * @class InteractionBox
 * @memberof Leap
 * @classdesc
 * The InteractionBox class represents a box-shaped region completely within
 * the field of view of the Leap Motion controller.
 *
 * The interaction box is an axis-aligned rectangular prism and provides
 * normalized coordinates for hands, fingers, and tools within this box.
 * The InteractionBox class can make it easier to map positions in the
 * Leap Motion coordinate system to 2D or 3D coordinate systems used
 * for application drawing.
 *
 * The InteractionBox region is defined by a center and dimensions along the x, y, and z axes.
 */
var InteractionBox = exports.InteractionBox = function(data) {
  /**
   * Indicates whether this is a valid InteractionBox object.
   *
   * @member valid
   * @type {Boolean}
   * @memberof Leap.InteractionBox.prototype
   */
  this.valid = true;
  /**
   * The center of the InteractionBox in device coordinates (millimeters).
   * This point is equidistant from all sides of the box.
   *
   * @member center
   * @type {number[]}
   * @memberof Leap.InteractionBox.prototype
   */
  this.center = data.center;

  this.size = data.size;
  /**
   * The width of the InteractionBox in millimeters, measured along the x-axis.
   *
   * @member width
   * @type {number}
   * @memberof Leap.InteractionBox.prototype
   */
  this.width = data.size[0];
  /**
   * The height of the InteractionBox in millimeters, measured along the y-axis.
   *
   * @member height
   * @type {number}
   * @memberof Leap.InteractionBox.prototype
   */
  this.height = data.size[1];
  /**
   * The depth of the InteractionBox in millimeters, measured along the z-axis.
   *
   * @member depth
   * @type {number}
   * @memberof Leap.InteractionBox.prototype
   */
  this.depth = data.size[2];
}

/**
 * Converts a position defined by normalized InteractionBox coordinates
 * into device coordinates in millimeters.
 *
 * This function performs the inverse of normalizePoint().
 *
 * @method denormalizePoint
 * @memberof Leap.InteractionBox.prototype
 * @param {Leap.Vector} normalizedPosition The input position in InteractionBox coordinates.
 * @returns {Leap.Vector} The corresponding denormalized position in device coordinates.
 */
InteractionBox.prototype.denormalizePoint = function(normalizedPosition) {
  return vec3.fromValues(
    (normalizedPosition[0] - 0.5) * this.size[0] + this.center[0],
    (normalizedPosition[1] - 0.5) * this.size[1] + this.center[1],
    (normalizedPosition[2] - 0.5) * this.size[2] + this.center[2]
  );
}

/**
 * Normalizes the coordinates of a point using the interaction box.
 *
 * Coordinates from the Leap Motion frame of reference (millimeters) are
 * converted to a range of [0..1] such that the minimum value of the
 * InteractionBox maps to 0 and the maximum value of the InteractionBox maps to 1.
 *
 * @method normalizePoint
 * @memberof Leap.InteractionBox.prototype
 * @param {Leap.Vector} position The input position in device coordinates.
 * @param {Boolean} clamp Whether or not to limit the output value to the range [0,1]
 * when the input position is outside the InteractionBox. Defaults to true.
 * @returns {Leap.Vector} The normalized position.
 */
InteractionBox.prototype.normalizePoint = function(position, clamp) {
  var vec = vec3.fromValues(
    ((position[0] - this.center[0]) / this.width) + 0.5,
    ((position[1] - this.center[1]) / this.height) + 0.5,
    ((position[2] - this.center[2]) / this.depth) + 0.5
  );

  if (clamp) {
    vec[0] = Math.min(Math.max(vec[0], 0), 1);
    vec[1] = Math.min(Math.max(vec[1], 0), 1);
    vec[2] = Math.min(Math.max(vec[2], 0), 1);
  }
  return vec;
}

/**
 * Writes a brief, human readable description of the InteractionBox object.
 *
 * @method toString
 * @memberof Leap.InteractionBox.prototype
 * @returns {String} A description of the InteractionBox object as a string.
 */
InteractionBox.prototype.toString = function() {
  return "InteractionBox [ width:" + this.width + " | height:" + this.height + " | depth:" + this.depth + " ]";
}

/**
 * An invalid InteractionBox object.
 *
 * You can use this InteractionBox instance in comparisons testing
 * whether a given InteractionBox instance is valid or invalid. (You can also use the
 * InteractionBox.valid property.)
 *
 * @static
 * @type {Leap.InteractionBox}
 * @name Invalid
 * @memberof Leap.InteractionBox
 */
InteractionBox.Invalid = { valid: false };
<|endoftext|>


<commit_before>// This may look like an empty string, but it's actually a single zero-width-space character.
module.exports = "";<commit_msg>next prefix char<commit_after>/**
 * http://en.wikipedia.org/wiki/Delimiter#ASCII_delimited_text
 * record separator character.
 */
module.exports = String.fromCharCode(30);
<|endoftext|>


<commit_before>import { Color } from "lively.graphics";
import { Morph, StyleSheet, Label, HorizontalLayout, config } from "lively.morphic";

export class TooltipViewer {

  constructor(world) {
    this.currenMorph = world;
  }

  notPartOfCurrentTooltip(newTarget) {
    return !newTarget.ownerChain().includes(this.currentMorph);
  }

  invalidatesCurrentTooltip(newTarget) {
    return newTarget.tooltip || this.notPartOfCurrentTooltip(newTarget);
  }

  mouseMove({targetMorph, hand}) {
    if (this.currentMorph === targetMorph
     || !this.invalidatesCurrentTooltip(targetMorph)) return;
    this.hoverOutOfMorph(this.currentMorph);
    this.hoverIntoMorph(targetMorph, hand);
    this.currentMorph = targetMorph;
  }

  mouseDown({targetMorph}) {
    this.currentTooltip && this.currentTooltip.remove();
    this.currentTooltip = null;
  }

  hoverIntoMorph(morph, hand) {
    this.clearScheduledTooltip();
    if (this.currentTooltip) {
      this.showTooltipFor(morph, hand);
    } else {
      this.scheduleTooltipFor(morph, hand);
    }
  }

  hoverOutOfMorph(morph) {
    const current = this.currentTooltip;
    this.currentTooltip && this.currentTooltip.softRemove((tooltip) =>
      this.currentTooltip == tooltip && (this.currentTooltip = null));
  }

  scheduleTooltipFor(morph, hand) {
    this.timer = setTimeout(
      () => this.showTooltipFor(morph, hand),
      config.showTooltipsAfter * 1000);
  }

  clearScheduledTooltip() {
    clearTimeout(this.timer);
  }

  showTooltipFor(morph, hand) {
    if (!morph.tooltip || !morph.world()) return;
    this.currentTooltip && this.currentTooltip.remove();
    var position = hand ? hand.position.addXY(10,7) : morph.globalBounds().bottomRight();
    this.currentTooltip = new Tooltip({position, description: morph.tooltip});
    morph.world().addMorph(this.currentTooltip);
  }

}

export class Tooltip extends Morph {

  static get styleSheet() {
    return new StyleSheet({
      ".Tooltip": {
        draggable: false,
        fill: Color.black.withA(0.5),
        borderRadius: 4,
        fontColor: Color.white,
        layout: new HorizontalLayout({spacing: 5})
      },
      '.Tooltip .Label': {
        fontColor: Color.white
      }
    })
  }

  static get properties() {
    return {
      description: {
        after: ['submorphs'],
        derived: true,
        get() {
          const [descriptor] = this.submorphs;
          return descriptor.value;
        },
        set(stringOrAttributes) {
          const [descriptor] = this.submorphs;
          descriptor.fixedWidth = stringOrAttributes.length > 40;
          descriptor.value = stringOrAttributes;
        }
      },
      submorphs: {
        initialize() {
          this.submorphs = [
            new Label({
              width: 200,
            })
          ];
        }
      }
    }
  }

  async softRemove(cb) {
    await this.animate({opacity: 0});
    cb && cb(this);
    this.remove();
  }

}
<commit_msg>fix tooltips for standalone compilation<commit_after>import { Color } from "lively.graphics";
import { Morph, StyleSheet, Label, HorizontalLayout, config } from "./index.js";

export class TooltipViewer {

  constructor(world) {
    this.currentMorph = world;
  }

  get __dont_serialialize__() {
    return ['currentTooltip', 'currentMorph'];
  }

  notPartOfCurrentTooltip(newTarget) {
    return !newTarget.ownerChain().includes(this.currentMorph);
  }

  invalidatesCurrentTooltip(newTarget) {
    return newTarget.tooltip || this.notPartOfCurrentTooltip(newTarget);
  }

  mouseMove({targetMorph, hand}) {
    if (this.currentMorph === targetMorph
     || !this.invalidatesCurrentTooltip(targetMorph)) return;
    this.hoverOutOfMorph(this.currentMorph);
    this.hoverIntoMorph(targetMorph, hand);
    this.currentMorph = targetMorph;
  }

  mouseDown({targetMorph}) {
    this.currentTooltip && this.currentTooltip.remove();
    this.currentTooltip = null;
  }

  hoverIntoMorph(morph, hand) {
    this.clearScheduledTooltip();
    if (this.currentTooltip) {
      this.showTooltipFor(morph, hand);
    } else {
      this.scheduleTooltipFor(morph, hand);
    }
  }

  hoverOutOfMorph(morph) {
    const current = this.currentTooltip;
    this.currentTooltip && this.currentTooltip.softRemove((tooltip) =>
      this.currentTooltip == tooltip && (this.currentTooltip = null));
  }

  scheduleTooltipFor(morph, hand) {
    this.timer = setTimeout(
      () => this.showTooltipFor(morph, hand),
      config.showTooltipsAfter * 1000);
  }

  clearScheduledTooltip() {
    clearTimeout(this.timer);
  }

  showTooltipFor(morph, hand) {
    if (!morph.tooltip || !morph.world()) return;
    this.currentTooltip && this.currentTooltip.remove();
    var position = hand ? hand.position.addXY(10,7) : morph.globalBounds().bottomRight();
    this.currentTooltip = new Tooltip({position, description: morph.tooltip});
    morph.world().addMorph(this.currentTooltip);
  }

}

export class Tooltip extends Morph {

  static get styleSheet() {
    return new StyleSheet({
      ".Tooltip": {
        draggable: false,
        fill: Color.black.withA(0.5),
        borderRadius: 4,
        fontColor: Color.white,
        layout: new HorizontalLayout({spacing: 5})
      },
      '.Tooltip .Label': {
        fontColor: Color.white
      }
    })
  }

  static get properties() {
    return {
      description: {
        after: ['submorphs'],
        derived: true,
        get() {
          const [descriptor] = this.submorphs;
          return descriptor.value;
        },
        set(stringOrAttributes) {
          const [descriptor] = this.submorphs;
          descriptor.fixedWidth = stringOrAttributes.length > 40;
          descriptor.value = stringOrAttributes;
        }
      },
      submorphs: {
        initialize() {
          this.submorphs = [
            new Label({
              width: 200,
            })
          ];
        }
      }
    }
  }

  async softRemove(cb) {
    await this.animate({opacity: 0});
    cb && cb(this);
    this.remove();
  }

}
<|endoftext|>


<commit_before>var async = require('async');
var listToArray = require('list-to-array');

var evalDependsOn = require('../../fields/utils/evalDependsOn.js');

var MONGO_INDEX_CONSTRAINT_ERROR_REGEXP = /E11000 duplicate key error index\: [^\$]+\$(\w+) dup key\: \{ \: "([^"]+)" \}/;

// Adds a validation message to the errors object in the common format
function addValidationError (options, errors, field, type, detail) {
	if (detail instanceof Error) {
		detail = detail.name !== 'Error' ? detail.name + ': ' + detail.message : detail.message;
	}
	var error = '';
	if (typeof detail === 'string') {
		error = detail;
	} else {
		if (type === 'required' && options.requiredMessages && options.requiredMessages[field.path]) {
			error = options.requiredMessages[field.path];
		} else if (type === 'invalid' && options.invalidMessages && options.invalidMessages[field.path]) {
			error = options.invalidMessages[field.path];
		} else {
			error = field.path.substr(0, 1).toUpperCase() + field.path.substr(1) + ' is ' + type;
		}
	}
	errors[field.path] = {
		type: type,
		error: error,
		detail: typeof detail === 'object' ? detail : undefined,
		fieldLabel: field.label,
		fieldType: field.type,
	};
};

// Adds a field update error message to the errors object in the common format
function addFieldUpdateError (errors, field, detail) {
	if (detail instanceof Error) {
		detail = detail.name !== 'Error' ? detail.name + ': ' + detail.message : detail.message;
	}
	errors[field.path] = {
		error: typeof detail === 'string' ? detail : field.path + ' error',
		detail: typeof detail === 'object' ? detail : undefined,
		fieldLabel: field.label,
		fieldType: field.type,
	};
}

function updateItem (item, data, options, callback) {
	/* Process arguments and options */
	if (typeof options === 'function') {
		callback = options;
		options = {};
	}

	// update fields with noedit: true set if fields have been explicitly
	// provided, or if the ignoreNoEdit option is true
	var ignoreNoEdit = !!(options.fields || options.ignoreNoEdit);

	// fields defaults to all the fields in the list
	var fields = options.fields || this.fieldsArray;
	// fields can be a list or array of field paths or Field instances
	fields = listToArray(fields).map(function (field) {
		// TODO: Check that field is an instance of Field
		return (typeof field === 'string') ? this.fields[field] : field;
	}, this);
	// check for invalid fields
	if (fields.indexOf(undefined) >= 0) {
		return callback({
			error: 'invalid configuration',
			detail: 'Invalid path specified in fields to update [' + options.fields + '] for list ' + this.key,
		});
	}

	// you can optionally require fields that aren't required in the schema
	// note that if fields are required in the schema, they will always be checked
	//
	// this option supports the backwards compatible { path: true } format, or a
	// list or array of field paths to validate
	var requiredFields = options.required;
	var requiredFieldPaths = {};
	if (typeof requiredFields === 'string') {
		requiredFields = listToArray(requiredFields);
	}
	if (Array.isArray(requiredFields)) {
		requiredFields.forEach(function (path) {
			requiredFieldPaths[path] = true;
		});
	} else if (typeof requiredFields === 'object') {
		requiredFieldPaths = requiredFields;
	}

	/* Field Validation */
	// TODO: If a field is required but not specified in the provided fields array
	// we should explicitly include it in the set of fields to validate
	var validationErrors = {};
	function doFieldValidation (field, done) {
		// only validate fields we're going to update
		if (field.noedit && !ignoreNoEdit) {
			return done();
		}
		// Note; we don't pass back validation errors to the callback, because we don't
		// want to break the async loop before all the fields have been validated.
		field.validateInput(data, function (valid, detail) {
			if (!valid) {
				addValidationError(options, validationErrors, field, 'invalid', detail);
				done();
			} else {
				if ((field.required || requiredFieldPaths[field.path])
					&& (!field.dependsOn || evalDependsOn(field.dependsOn, data)))
				{
					field.validateRequiredInput(item, data, function (valid, detail) {
						if (!valid) {
							addValidationError(options, validationErrors, field, 'required', detail);
						}
						done();
					});
				} else {
					done();
				}
			}
		});
	}

	/* Field Updates */
	var updateErrors = {};
	function doFieldUpdate (field, done) {
		var callback = function (err) {
			// Note; we don't pass back errors to the callback, because we don't want
			// to break the async loop before all the fields have been updated.
			if (err) {
				addFieldUpdateError(updateErrors, field, err);
			}
			done();
		};
		// check we should update the field
		if (field.noedit && !ignoreNoEdit) {
			return done();
		}
		// all fields have (item, data) as the first two arguments
		var updateArgs = [item, data];
		// some fields support an optional third argument: files
		if (field.updateItem.length > 3) {
			updateArgs.push(options.files);
		}
		// callback is always the last argument
		updateArgs.push(callback);
		// call field.updateItem with the arguments
		field.updateItem.apply(field, updateArgs);
	}

	/* Track plugin support */
	// If the track plugin is enabled for the list, it looks for ._req_user to
	// detect the user that performed the updated. Default it to the user
	// specified in the options.
	if (options.user) {
		item._req_user = options.user;
	}

	/* Flow control */
	async.series([
		/* Process validation */
		function (passedValidation) {
			async.each(fields, doFieldValidation, function () {
				if (Object.keys(validationErrors).length) {
					return passedValidation({
						error: 'validation errors',
						detail: validationErrors,
					});
				}
				passedValidation();
			});
		},
		/* Apply updates to fields */
		function (doneUpdate) {
			async.each(fields, doFieldUpdate, function () {
				if (Object.keys(updateErrors).length) {
					return doneUpdate({
						error: 'field errors',
						detail: updateErrors,
					});
				}
				item.save(doneUpdate);
			});
		},
	],

	/* Done */
	function (err) {
		if (err) {
			if (err instanceof Error) {
				// Try to make mongoose index constraint errors more friendly
				// This is brittle, but should return a more human-readable error message
				if (err.code === 11000) {
					var indexConstraintError = MONGO_INDEX_CONSTRAINT_ERROR_REGEXP.exec(err.errmsg);
					if (indexConstraintError) {
						var probableFieldPath = indexConstraintError[1];
						probableFieldPath = probableFieldPath.substr(0, probableFieldPath.lastIndexOf('_'));
						return callback({
							error: 'database error',
							detail: 'Duplicate ' + probableFieldPath + ' value "' + indexConstraintError[2] + '" already exists',
						});
					}
				}
				// Wrap Error objects in the standard format, they're most likely
				// a database error (not sure if we can make this more specific?)
				return callback({
					error: 'database error',
					detail: err,
				});
			} else {
				// Return other error object directly
				return callback(err);
			}
		}
		return callback();
	});
}

module.exports = updateItem;
<commit_msg>Improves handling of noedit, fixes #3575<commit_after>var async = require('async');
var listToArray = require('list-to-array');

var evalDependsOn = require('../../fields/utils/evalDependsOn.js');

var MONGO_INDEX_CONSTRAINT_ERROR_REGEXP = /E11000 duplicate key error index\: [^\$]+\$(\w+) dup key\: \{ \: "([^"]+)" \}/;

// Adds a validation message to the errors object in the common format
function addValidationError (options, errors, field, type, detail) {
	if (detail instanceof Error) {
		detail = detail.name !== 'Error' ? detail.name + ': ' + detail.message : detail.message;
	}
	var error = '';
	if (typeof detail === 'string') {
		error = detail;
	} else {
		if (type === 'required' && options.requiredMessages && options.requiredMessages[field.path]) {
			error = options.requiredMessages[field.path];
		} else if (type === 'invalid' && options.invalidMessages && options.invalidMessages[field.path]) {
			error = options.invalidMessages[field.path];
		} else {
			error = field.path.substr(0, 1).toUpperCase() + field.path.substr(1) + ' is ' + type;
		}
	}
	errors[field.path] = {
		type: type,
		error: error,
		detail: typeof detail === 'object' ? detail : undefined,
		fieldLabel: field.label,
		fieldType: field.type,
	};
};

// Adds a field update error message to the errors object in the common format
function addFieldUpdateError (errors, field, detail) {
	if (detail instanceof Error) {
		detail = detail.name !== 'Error' ? detail.name + ': ' + detail.message : detail.message;
	}
	errors[field.path] = {
		error: typeof detail === 'string' ? detail : field.path + ' error',
		detail: typeof detail === 'object' ? detail : undefined,
		fieldLabel: field.label,
		fieldType: field.type,
	};
}

function updateItem (item, data, options, callback) {
	/* Process arguments and options */
	if (typeof options === 'function') {
		callback = options;
		options = {};
	}

	// update fields with noedit: true set if fields have been explicitly
	// provided, or if the ignoreNoEdit option is true
	var ignoreNoEdit = !!(options.fields || options.ignoreNoEdit);

	// fields defaults to all the fields in the list
	var fields = options.fields || this.fieldsArray;
	// fields can be a list or array of field paths or Field instances
	fields = listToArray(fields).map(function (field) {
		// TODO: Check that field is an instance of Field
		return (typeof field === 'string') ? this.fields[field] : field;
	}, this);
	// check for invalid fields
	if (fields.indexOf(undefined) >= 0) {
		return callback({
			error: 'invalid configuration',
			detail: 'Invalid path specified in fields to update [' + options.fields + '] for list ' + this.key,
		});
	}

	// Strip out noedit fields
	if (!ignoreNoEdit) {
		fields = fields.filter(i => !i.noedit);
	}

	// you can optionally require fields that aren't required in the schema
	// note that if fields are required in the schema, they will always be checked
	//
	// this option supports the backwards compatible { path: true } format, or a
	// list or array of field paths to validate
	var requiredFields = options.required;
	var requiredFieldPaths = {};
	if (typeof requiredFields === 'string') {
		requiredFields = listToArray(requiredFields);
	}
	if (Array.isArray(requiredFields)) {
		requiredFields.forEach(function (path) {
			requiredFieldPaths[path] = true;
		});
	} else if (typeof requiredFields === 'object') {
		requiredFieldPaths = requiredFields;
	}

	/* Field Validation */
	// TODO: If a field is required but not specified in the provided fields array
	// we should explicitly include it in the set of fields to validate
	var validationErrors = {};
	function doFieldValidation (field, done) {
		// Note; we don't pass back validation errors to the callback, because we don't
		// want to break the async loop before all the fields have been validated.
		field.validateInput(data, function (valid, detail) {
			if (!valid) {
				addValidationError(options, validationErrors, field, 'invalid', detail);
				done();
			} else {
				if ((field.required || requiredFieldPaths[field.path])
					&& (!field.dependsOn || evalDependsOn(field.dependsOn, data)))
				{
					field.validateRequiredInput(item, data, function (valid, detail) {
						if (!valid) {
							addValidationError(options, validationErrors, field, 'required', detail);
						}
						done();
					});
				} else {
					done();
				}
			}
		});
	}

	/* Field Updates */
	var updateErrors = {};
	function doFieldUpdate (field, done) {
		var callback = function (err) {
			// Note; we don't pass back errors to the callback, because we don't want
			// to break the async loop before all the fields have been updated.
			if (err) {
				addFieldUpdateError(updateErrors, field, err);
			}
			done();
		};
		// all fields have (item, data) as the first two arguments
		var updateArgs = [item, data];
		// some fields support an optional third argument: files
		if (field.updateItem.length > 3) {
			updateArgs.push(options.files);
		}
		// callback is always the last argument
		updateArgs.push(callback);
		// call field.updateItem with the arguments
		field.updateItem.apply(field, updateArgs);
	}

	/* Track plugin support */
	// If the track plugin is enabled for the list, it looks for ._req_user to
	// detect the user that performed the updated. Default it to the user
	// specified in the options.
	if (options.user) {
		item._req_user = options.user;
	}

	/* Flow control */
	async.series([
		/* Process validation */
		function (doneValidation) {
			async.each(fields, doFieldValidation, function () {
				if (Object.keys(validationErrors).length) {
					return doneValidation({
						error: 'validation errors',
						detail: validationErrors,
					});
				}
				doneValidation();
			});
		},
		/* Apply updates to fields */
		function (doneUpdate) {
			async.each(fields, doFieldUpdate, function () {
				if (Object.keys(updateErrors).length) {
					return doneUpdate({
						error: 'field errors',
						detail: updateErrors,
					});
				}
				item.save(doneUpdate);
			});
		},
	],

	/* Done */
	function (err) {
		if (err) {
			if (err instanceof Error) {
				// Try to make mongoose index constraint errors more friendly
				// This is brittle, but should return a more human-readable error message
				if (err.code === 11000) {
					var indexConstraintError = MONGO_INDEX_CONSTRAINT_ERROR_REGEXP.exec(err.errmsg);
					if (indexConstraintError) {
						var probableFieldPath = indexConstraintError[1];
						probableFieldPath = probableFieldPath.substr(0, probableFieldPath.lastIndexOf('_'));
						return callback({
							error: 'database error',
							detail: 'Duplicate ' + probableFieldPath + ' value "' + indexConstraintError[2] + '" already exists',
						});
					}
				}
				// Wrap Error objects in the standard format, they're most likely
				// a database error (not sure if we can make this more specific?)
				return callback({
					error: 'database error',
					detail: err,
				});
			} else {
				// Return other error object directly
				return callback(err);
			}
		}
		return callback();
	});
}

module.exports = updateItem;
<|endoftext|>


<commit_before>module.exports = function (method, settings, callback = () => {}) {
  const { access_token = this.settings.token } = settings
  const code = `API.${method}(${JSON.stringify(settings)})`

  this.methods = [
    ...this.methods.filter(item => item.access_token !== access_token),
    {
      access_token,
      items: [
        ...this.methods.filter(item => item.access_token === access_token),
        { code, callback }
      ]
    }
  ]

  return this
}
<commit_msg>[fix] execute fix (?)<commit_after>module.exports = function (method, settings, callback = () => {}) {
  const { access_token = this.settings.token } = settings
  const code = `API.${method}(${JSON.stringify(settings)})`

  const otherTokenItems = this.methods.filter(item => item.access_token !== access_token)
  const currentTokenItems = this.methods.find(item => item.access_token === access_token)

  this.methods = [
    ...otherTokenItems,
    {
      access_token,
      items: [
        ...(currentTokenItems ? currentTokenItems.items : []),
        { code, callback }
      ]
    }
  ]

  return this
}
<|endoftext|>


<commit_before>/**
 *  class Manifest
 *
 *  The Manifest logs the contents of assets compiled to a single
 *  directory. It records basic attributes about the asset for fast
 *  lookup without having to compile. A pointer from each logical path
 *  indicates with fingerprinted asset is the current one.
 *
 *  The JSON is part of the public API and should be considered
 *  stable. This should make it easy to read from other programming
 *  languages and processes that don't have sprockets loaded. See
 *  `#assets` and `#files` for more infomation about the structure.
 **/

'use strict';

// stdlib
var fs   = require('fs');
var path = require('path');

// 3rd-party
var _         = require('underscore');
var async     = require('async');
var mkdirp    = require('mkdirp');

// internal
var prop        = require('./common').prop;
var getter      = require('./common').getter;
var isAbsolute  = require('./common').isAbsolute;
var logger      = require('./logger');

/**
 *  new Manifest(environment, path)
 *
 *  Create new Manifest associated with an `environment`. `path` is
 *  a full path to the manifest json file. The file may or may not
 *  already exist. The dirname of the `path` will be used to write
 *  compiled assets to. Otherwise, if the path is a directory, the
 *  filename will default to "manifest.json" in that directory.
 *
 *    new Manifest(environment, "./public/assets/manifest.json");
 **/
var Manifest = module.exports = function Manifest(environment, pathname) {
  var data;

  prop(this, 'environment', environment);
  prop(this, 'logger',      logger);

  if ('' === path.extname(path)) {
    prop(this, 'dir',  path.resolve(pathname));
    prop(this, 'path', path.join(this.dir, 'manifest.json'));
  } else {
    prop(this, 'dir',   path.dirname(pathname));
    prop(this, 'path',  path.resolve(pathname));
  }

  if (path.existsSync(this.path)) {
    try {
      data = require(this.path);
    } catch (err) {
      // TODO: Replace with some kind of internal (configurable) logger
      this.logger.error(this.path + " is invalid: " + err);
    }
  }

  prop(this, 'data', (!!data && _.isObject(data)) ? data : {});
};

/**
 *  Manifest#assets -> Object
 *
 *  Returns internal assets mapping. Keys are logical paths which
 *  map to the latest fingerprinted filename.
 *
 *    Logical path (String): Fingerprint path (String)
 *
 *    { "application.js" => "application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js",
 *      "jquery.js"      => "jquery-ae0908555a245f8266f77df5a8edca2e.js" }
 **/
getter(Manifest.prototype, 'assets', function () {
  if (!this.data.assets) {
    this.data.assets = {};
  }

  return this.data.assets;
});

/**
 *  Manifest#files -> Object
 *
 *  Returns internal file directory listing. Keys are filenames
 *  which map to an attributes array.
 *
 *    Fingerprint path (String):
 *      logical_path: Logical path (String)
 *      mtime: ISO8601 mtime (String)
 *      digest: Base64 hex digest (String)
 *
 *   { "application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js" =>
 *       { 'logical_path' => "application.js",
 *         'mtime' => "2011-12-13T21:47:08-06:00",
 *         'digest' => "2e8e9a7c6b0aafa0c9bdeec90ea30213" } }
 **/
getter(Manifest.prototype, 'files', function () {
  if (!this.data.files) {
    this.data.files = {};
  }

  return this.data.files;
});

// Basic wrapper around Environment#findAsset and Environment#compile.
// Logs compile time.
Manifest.prototype.compileAsset = function (logicalPath, callback) {
  var self  = this,
      start = Date.now(),
      asset = self.environment.findAsset(logicalPath);

  if (!asset) {
    callback(new Error("Can not find asset '" + logicalPath + "'"));
    return;
  }

  asset.compile(function (err, asset) {
    var ms;

    if (err) {
      callback(err);
      return;
    }

    ms = +((Date.now() - start) / 1000);
    self.logger.warn('Compiled ' + logicalPath + ' (' + ms + 'ms)');

    callback(err, asset);
  });
};

/**
 *  Manifest#compile(*files[, callback]) -> Void
 *
 *  Compile and write asset(s) to directory. The asset is written to a
 *  fingerprinted filename like `app-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`.
 *  An entry is also inserted into the manifest file.
 *
 *    manifest.compile("app.js", function (err, data) {
 *      // ...
 *    });
 **/
Manifest.prototype.compile = function () {
  var self     = this,
      files    = _.flatten(arguments),
      callback = _.isFunction(_.last(files)) ? files.pop() : function () {},
      paths    = [];

  this.environment.eachLogicalPath(files, function (pathname) {
    if (pathname) {
      paths.push(pathname);
    } else {
      // FIXME: How this can be possible???
      return;
    }
  });

  paths = _.union(paths, _.select(files, isAbsolute));

  async.forEachSeries(paths, function (logicalPath, next) {
    self.compileAsset(logicalPath, function(err, asset) {
      var target;

      if (err) {
        callback(err);
        return;
      }

      target = path.join(self.dir, asset.digestPath);

      self.assets[asset.logicalPath]  = asset.digestPath;
      self.files[asset.digestPath]    = {
        logical_path:   asset.logicalPath,
        mtime:          asset.mtime.toISOString(),
        size:           asset.bytesize,
        digest:         asset.digest
      };

      if (path.existsSync(target)) {
        self.logger.debug('Skipping ' + target + ', already exists');
        self.save();
        next();
      } else {
        self.logger.info('Writing ' + target);
        asset.writeTo(target, {}, function (err) {
          if (err) {
            next(err);
            return;
          }

          self.save();
          next();
        });
      }
    });
  }, function (err) {
    callback(err, self.data);
  });
};

// Persist manifest back to FS
Manifest.prototype.save = function () {
  mkdirp.sync(this.dir);
  fs.writeFileSync(this.path, JSON.stringify(this.data));
};
<commit_msg>Refactor manifest<commit_after>/**
 *  class Manifest
 *
 *  The Manifest logs the contents of assets compiled to a single
 *  directory. It records basic attributes about the asset for fast
 *  lookup without having to compile. A pointer from each logical path
 *  indicates with fingerprinted asset is the current one.
 *
 *  The JSON is part of the public API and should be considered
 *  stable. This should make it easy to read from other programming
 *  languages and processes that don't have sprockets loaded. See
 *  `#assets` and `#files` for more infomation about the structure.
 **/

'use strict';

// stdlib
var fs   = require('fs');
var path = require('path');

// 3rd-party
var _         = require('underscore');
var async     = require('async');
var mkdirp    = require('mkdirp');

// internal
var prop        = require('./common').prop;
var getter      = require('./common').getter;
var isAbsolute  = require('./common').isAbsolute;
var logger      = require('./logger');

/**
 *  new Manifest(environment, path)
 *
 *  Create new Manifest associated with an `environment`. `path` is
 *  a full path to the manifest json file. The file may or may not
 *  already exist. The dirname of the `path` will be used to write
 *  compiled assets to. Otherwise, if the path is a directory, the
 *  filename will default to "manifest.json" in that directory.
 *
 *    new Manifest(environment, "./public/assets/manifest.json");
 **/
var Manifest = module.exports = function Manifest(environment, pathname) {
  var data;

  prop(this, 'environment', environment);
  prop(this, 'logger',      logger);

  if ('' === path.extname(path)) {
    prop(this, 'dir',  path.resolve(pathname));
    prop(this, 'path', path.join(this.dir, 'manifest.json'));
  } else {
    prop(this, 'dir',   path.dirname(pathname));
    prop(this, 'path',  path.resolve(pathname));
  }

  if (path.existsSync(this.path)) {
    try {
      data = require(this.path);
    } catch (err) {
      // TODO: Replace with some kind of internal (configurable) logger
      this.logger.error(this.path + " is invalid: " + err);
    }
  }

  prop(this, 'data', (!!data && _.isObject(data)) ? data : {});
};

/**
 *  Manifest#assets -> Object
 *
 *  Returns internal assets mapping. Keys are logical paths which
 *  map to the latest fingerprinted filename.
 *
 *    Logical path (String): Fingerprint path (String)
 *
 *    { "application.js" => "application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js",
 *      "jquery.js"      => "jquery-ae0908555a245f8266f77df5a8edca2e.js" }
 **/
getter(Manifest.prototype, 'assets', function () {
  if (!this.data.assets) {
    this.data.assets = {};
  }

  return this.data.assets;
});

/**
 *  Manifest#files -> Object
 *
 *  Returns internal file directory listing. Keys are filenames
 *  which map to an attributes array.
 *
 *    Fingerprint path (String):
 *      logical_path: Logical path (String)
 *      mtime: ISO8601 mtime (String)
 *      digest: Base64 hex digest (String)
 *
 *   { "application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js" =>
 *       { 'logical_path' => "application.js",
 *         'mtime' => "2011-12-13T21:47:08-06:00",
 *         'digest' => "2e8e9a7c6b0aafa0c9bdeec90ea30213" } }
 **/
getter(Manifest.prototype, 'files', function () {
  if (!this.data.files) {
    this.data.files = {};
  }

  return this.data.files;
});

// Basic wrapper around Environment#findAsset and Environment#compile.
// Logs compile time.
Manifest.prototype.compileAsset = function (logicalPath, callback) {
  var self  = this,
      start = Date.now(),
      asset = self.environment.findAsset(logicalPath);

  if (!asset) {
    callback(new Error("Can not find asset '" + logicalPath + "'"));
    return;
  }

  asset.compile(function (err, asset) {
    var ms;

    if (err) {
      callback(err);
      return;
    }

    ms = +((Date.now() - start) / 1000);
    self.logger.warn('Compiled ' + logicalPath + ' (' + ms + 'ms)');

    callback(err, asset);
  });
};

/**
 *  Manifest#compile(*files[, callback]) -> Void
 *
 *  Compile and write asset(s) to directory. The asset is written to a
 *  fingerprinted filename like `app-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`.
 *  An entry is also inserted into the manifest file.
 *
 *    manifest.compile("app.js", function (err, data) {
 *      // ...
 *    });
 **/
Manifest.prototype.compile = function () {
  var self     = this,
      files    = _.flatten(arguments),
      callback = _.isFunction(_.last(files)) ? files.pop() : function () {},
      paths    = [];

  this.environment.eachLogicalPath(files, function (pathname) {
    if (pathname) {
      paths.push(pathname);
    } else {
      // FIXME: How this can be possible???
      return;
    }
  });

  paths = _.union(paths, _.select(files, isAbsolute));

  async.forEachSeries(paths, function (logicalPath, next) {
    self.compileAsset(logicalPath, function(err, asset) {
      var target;

      if (err) {
        callback(err);
        return;
      }

      target = path.join(self.dir, asset.digestPath);

      self.assets[asset.logicalPath]  = asset.digestPath;
      self.files[asset.digestPath]    = {
        logical_path:   asset.logicalPath,
        mtime:          asset.mtime.toISOString(),
        size:           asset.bytesize,
        digest:         asset.digest
      };

      path.exists(target, function (exists) {
        if (exists) {
          self.logger.debug('Skipping ' + target + ', already exists');
          self.save(next);
          return;
        }

        self.logger.info('Writing ' + target);
        async.series([
          function (next) { asset.writeTo(target, {}, next); },
          function (next) { self.save(next); }
        ], next);
      });
    });
  }, function (err) {
    callback(err, self.data);
  });
};

// Persist manifest back to FS
Manifest.prototype.save = function (callback) {
  async.series([
    async.apply(mkdirp, this.dir),
    async.apply(fs.writeFile, this.path, JSON.stringify(this.data))
  ], callback);
};
<|endoftext|>


<commit_before>var http        = require('http')
  , querystring = require('querystring')

  , cookies     = require('cookies')
  , _           = require('underscore')
  ;

var MIXPANEL_API_HOST = 'api.mixpanel.com'
  , MIXPANEL_API_PORT = 80
  ;

var GlobalAPIToken
  ;

/* ========================================================================== *
 *  Static Utils Methods                                                      *
 * ========================================================================== */
function getUnixTime() {
  return (Date.now() + '').slice(0,-3) / 1;
}

/* ========================================================================== *
 *  Private Methods                                                           *
 * ========================================================================== */
MixpanelClient.prototype._saveSuperProperties = function() {
  this.cookieJar && this.cookieJar.set(this.MIXPANEL_COOKIE_NAME, this.superProperties);
}

MixpanelClient.prototype._sendRequest = function(endpointURI, data, callback) {
  var properties  = data.properties
    , requestData
    ;

  properties.token  || (properties.token  = this.APIToken                     );
  properties.time   || (properties.time   = getUnixTime()                     );
  properties.ip     || (properties.ip     = this.req.connection.remoteAddress );

  requestData = {
      data  : (new Buffer(JSON.stringify(data))).toString('base64')
    , t     : 0
  };

  http.get(
      { host  : MIXPANEL_API_HOST
      , port  : MIXPANEL_API_PORT
      , path  : endpointURI + '?' + querystring.stringify(requestData)
      }

    , function(res) {
        var data = ''
          ;

        res.on('data', function(chunk) { data += chunk; });

        res.on('end', function() {
            var err
              ;

            data == '1' || (err = new Error('MIXPANEL_API_REQUEST_ERROR: ' + data));

            err && console.log(err);
            callback && callback(err);
        });
      }

  ).on('error', function(err) {
    err && console.log(err);
    callback && callback(err);
  });

}

/* ========================================================================== *
 *  Public Methods                                                            *
 * ========================================================================== */

/***
 * Track an event caused by a user. This is the most important Mixpanel function
 * and is the one you will be using the most.
 *
 * @param {String} eventName The name of the event to track. This can be
 *     anything a user does - "button click", "user signup", "item purchased",
 *     etc. You can name your events anything you like.
 *
 * @param {Object} properties (optional) A set of properties to include with the
 *     event you're sending. These can describe the user who did the event or
 *     the event itself.
 */
MixpanelClient.prototype.track = function(eventName, properties, callback) {
  var compiledProperties
    ;

  compiledProperties = {
      event       : eventName
    , properties  : _.extend(new Object(this.superProperties), properties)
  };

  this._sendRequest('/track', compiledProperties, callback);
};

/***
 * Store a persistent set of properties for a user (i.e. super properties).
 * These properties are automatically included with all events sent by the user.
 *
 * @param {Object} properties A dictionary of information about the user to
 *     store. This is often information you just learned, such as the user's age
 *     or gender, that you'd like to send with later events.
 */
MixpanelClient.prototype.register = function(properties) {
  for (var key in properties) {
    this.superProperties[key] = properties[key];
  }

  this._saveSuperProperties();
};

/***
 * Store a persistent set of properties about a user, but only save them if they
 * haven't been set before. Useful for storing one-time values, or when you want
 * first-touch attribution.
 *
 * @param {Object} properties A dictionary of information about the user to
 *     store. This is often information you just learned, such as the user's age
 *     or gender, that you'd like to send with later events.
 *
 * @param {*} (Optinal) If the current value of the super property is this
 *     default value (ex: "False", "None") and a different value is set, we will
 *     override it. Defaults to 'undefined'.
 */
MixpanelClient.prototype.register_once = function(properties, defaultValue) {

  for (var key in properties) {
    if (!this.superProperties.hasOwnProperty(key) || (defaultValue && this.superProperties[key] === defaultValue)) {
      newProperties[key] = properties[key];
    }
  }

  return this.register(newProperties);
};

/***
 * Delete a super property stored on this user, if it exists.
 *
 * @param {String} propertyName The name of the super property to remove. If it
 *     doesn't exist, this call will do nothing.
 */
MixpanelClient.prototype.unregister = function(propertyName) {
  var count = 0
    ;

  this.superProperties.hasOwnProperty(key) && (count++);

  delete this.superProperties[propertyName];
  this._saveSuperProperties();

  return count;
};

/***
 * Get the value of a super property by the property name.
 *
 * @param {String} propertyName The name of the super property to retrieve.
 */
MixpanelClient.prototype.get_property = function(propertyName) {
  return this.superProperties[propertyName];
};

/***
 * Identify a user with a unique ID. All subsequent events sent by this user
 * will be tied to the new identity. If this method is not called, unique users
 * will be identified by a UUID generated the first time they visit your site.
 *
 * @param {String} uniqueId A string that uniquely identifies the user, such as
 *     the user's ID in your database.
 */
MixpanelClient.prototype.identify = function(uniqueId) {
  return this.register({ 'distinct_id' : uniqueId });
};

/***
 * Set a human-readable name for the user to be displayed in the Streams
 * interface. Name tags do not have to be unique.
 *
 * @param {String} nameTag A human-readable name for the user. This name will
 *     show up in the Mixpanel Streams interface.
 */
MixpanelClient.prototype.name_tag = function(nameTag) {
  return this.register({ 'mp_name_tag' : nameTag });
};

/* ========================================================================== *
 *  Static Public Methods                                                     *
 * ========================================================================== */
MixpanelClient.setAPIToken = function(APIToken) {
  globalAPIToken = APIToken;
};

/* ========================================================================== *
 *  MixpanelClient's Constructor                                              *
 * ========================================================================== */

/***
 * MixpanelClient's Constructor
 *
 * @param {String} APIToken The mixpanel API token.
 * @param {Object} req The `req` object as passed by express/connect to their
 *     middlewares.
 * @param {Object} res The `res` object as passed by express/connect to their
 *     middlewares.
 */
function MixpanelClient() {
  var args      = Array.prototype.slice.call(arguments)
    , APIToken  = typeof args[0] === 'string' ? args.shift() : undefined

    , res       = args.shift()
    , req       = args.shift()
    ;

  this.req                  = req;
  this.res                  = res;

  this.APIToken             = APIToken || globalAPIToken;

  this.MIXPANEL_COOKIE_NAME = 'mp' + this.APIToken + '_mixpanel';

  this.cookieJar            = this.req && this.res && new cookies(req, res);
  this.superProperties      = this.cookieJar && this.cookieJar.get(this.MIXPANEL_COOKIE_NAME) || {};
}

/* ========================================================================== */
/* ========================================================================== */

module.exports = MixpanelClient;<commit_msg>FIXED - Multiple bugs<commit_after>var http        = require('http')
  , querystring = require('querystring')

  , cookies     = require('cookies')
  , _           = require('underscore')
  ;

var MIXPANEL_API_HOST = 'api.mixpanel.com'
  , MIXPANEL_API_PORT = 80
  ;

var GlobalAPIToken
  ;

/* ========================================================================== *
 *  Static Utils Methods                                                      *
 * ========================================================================== */
function getUnixTime() {
  return (Date.now() + '').slice(0,-3) / 1;
}

/* ========================================================================== *
 *  Private Methods                                                           *
 * ========================================================================== */
MixpanelClient.prototype._saveSuperProperties = function() {
  if (this.cookieJar) {
    this.cookieJar.set(
        this.MIXPANEL_COOKIE_NAME
      , encodeURIComponent(JSON.stringify(this.superProperties))
      , this.COOKIE_OPTIONS
    );
  }
}

MixpanelClient.prototype._sendRequest = function(endpointURI, data, callback) {
  var properties  = data.properties
    , requestData
    ;

  properties.token  || (properties.token  = this.APIToken                     );
  properties.time   || (properties.time   = getUnixTime()                     );
  properties.ip     || (properties.ip     = this.req.connection.remoteAddress );

  requestData = {
      data  : new Buffer(JSON.stringify(data)).toString('base64')
    , t     : 0
  };

  http.get(
      { host  : MIXPANEL_API_HOST
      , port  : MIXPANEL_API_PORT
      , path  : endpointURI + '?' + querystring.stringify(requestData)
      }

    , function(res) {
        var data = ''
          ;

        res.on('data', function(chunk) { data += chunk; });

        res.on('end', function() {
            var err
              ;

            data == '1' || (err = new Error('MIXPANEL_API_REQUEST_ERROR: ' + data));

            err && console.log(err);
            callback && callback(err);
        });
      }

  ).on('error', function(err) {
    err && console.log('MIXPANEL_API_REQUEST_ERROR:' + err);
    callback && callback(err);
  });

}

/* ========================================================================== *
 *  Public Methods                                                            *
 * ========================================================================== */

/***
 * Track an event caused by a user. This is the most important Mixpanel function
 * and is the one you will be using the most.
 *
 * @param {String} eventName The name of the event to track. This can be
 *     anything a user does - "button click", "user signup", "item purchased",
 *     etc. You can name your events anything you like.
 *
 * @param {Object} properties (optional) A set of properties to include with the
 *     event you're sending. These can describe the user who did the event or
 *     the event itself.
 */
MixpanelClient.prototype.track = function(eventName, properties, callback) {
  var compiledProperties
    ;

  compiledProperties = {
      event       : eventName
    , properties  : _.extend(new Object(this.superProperties), properties)
  };

  this._sendRequest('/track', compiledProperties, callback);
};

/***
 * Store a persistent set of properties for a user (i.e. super properties).
 * These properties are automatically included with all events sent by the user.
 *
 * @param {Object} properties A dictionary of information about the user to
 *     store. This is often information you just learned, such as the user's age
 *     or gender, that you'd like to send with later events.
 */
MixpanelClient.prototype.register = function(properties) {
  for (var key in properties) {
    this.superProperties[key] = properties[key];
  }

  this._saveSuperProperties();
};

/***
 * Store a persistent set of properties about a user, but only save them if they
 * haven't been set before. Useful for storing one-time values, or when you want
 * first-touch attribution.
 *
 * @param {Object} properties A dictionary of information about the user to
 *     store. This is often information you just learned, such as the user's age
 *     or gender, that you'd like to send with later events.
 *
 * @param {*} (Optinal) If the current value of the super property is this
 *     default value (ex: "False", "None") and a different value is set, we will
 *     override it. Defaults to 'undefined'.
 */
MixpanelClient.prototype.register_once = function(properties, defaultValue) {

  for (var key in properties) {
    if (!this.superProperties.hasOwnProperty(key) || (defaultValue && this.superProperties[key] === defaultValue)) {
      newProperties[key] = properties[key];
    }
  }

  return this.register(newProperties);
};

/***
 * Delete a super property stored on this user, if it exists.
 *
 * @param {String} propertyName The name of the super property to remove. If it
 *     doesn't exist, this call will do nothing.
 */
MixpanelClient.prototype.unregister = function(propertyName) {
  var count = 0
    ;

  this.superProperties.hasOwnProperty(key) && (count++);

  delete this.superProperties[propertyName];
  this._saveSuperProperties();

  return count;
};

/***
 * Get the value of a super property by the property name.
 *
 * @param {String} propertyName The name of the super property to retrieve.
 */
MixpanelClient.prototype.get_property = function(propertyName) {
  return this.superProperties[propertyName];
};

/***
 * Identify a user with a unique ID. All subsequent events sent by this user
 * will be tied to the new identity. If this method is not called, unique users
 * will be identified by a UUID generated the first time they visit your site.
 *
 * @param {String} uniqueId A string that uniquely identifies the user, such as
 *     the user's ID in your database.
 */
MixpanelClient.prototype.identify = function(uniqueId) {
  return this.register({ 'distinct_id' : uniqueId });
};

/***
 * Set a human-readable name for the user to be displayed in the Streams
 * interface. Name tags do not have to be unique.
 *
 * @param {String} nameTag A human-readable name for the user. This name will
 *     show up in the Mixpanel Streams interface.
 */
MixpanelClient.prototype.name_tag = function(nameTag) {
  return this.register({ 'mp_name_tag' : nameTag });
};

/* ========================================================================== *
 *  Static Public Methods                                                     *
 * ========================================================================== */
MixpanelClient.setAPIToken = function(APIToken) {
  globalAPIToken = APIToken;
};

/* ========================================================================== *
 *  MixpanelClient's Constructor                                              *
 * ========================================================================== */

/***
 * MixpanelClient's Constructor
 *
 * @param {String} APIToken The mixpanel API token.
 * @param {Object} req The `req` object as passed by express/connect to their
 *     middlewares.
 * @param {Object} res The `res` object as passed by express/connect to their
 *     middlewares.
 */
function MixpanelClient() {
  var args      = Array.prototype.slice.call(arguments)
    , APIToken  = typeof args[0] === 'string' ? args.shift() : undefined

    , req       = args.shift()
    , res       = args.shift()
    ;

  this.req                  = req;
  this.res                  = res;

  this.APIToken             = APIToken || globalAPIToken;

  this.MIXPANEL_COOKIE_NAME = 'mp_' + this.APIToken + '_mixpanel';

  this.COOKIE_OPTIONS = {
      secure    : false
    , httpOnly  : false
    , expires   : new Date(Date.now() + 365 * 24 * 60 * 60 * 1000)
    , domain    : '.' + this.req.headers.host.split('.').slice(-2).join('.')
  };

  this.cookieJar            = this.req && this.res ? new cookies(req, res) : undefined;

  this.superProperties      = this.cookieJar && this.cookieJar.get(this.MIXPANEL_COOKIE_NAME) ? JSON.parse(decodeURIComponent(this.cookieJar.get(this.MIXPANEL_COOKIE_NAME))) : {};

}

/* ========================================================================== */
/* ========================================================================== */

module.exports = MixpanelClient;<|endoftext|>