<commit_before>var http = require('http');
var fs = require('fs');
var handlebars = require('handlebars');
var qs = require('querystring');

// setup the database functions based on which database is being used
var database = 'mongoDB'; // change to postgreSQL to use postgres instead
var MongoClient = null;
var sanitize = null;
var pg = null;
var updateCoins = null;
var displayCharacters = null;
var displayOneCharacter = null;
var insertCharacter = null;
if (database === 'mongoDB') {
    MongoClient = require('mongodb').MongoClient;
    sanitize = require('mongo-sanitize');
    updateCoins = updateCoinsMongo;
    displayCharacters = displayCharactersMongo;
    displayOneCharacter = displayOneCharacterMongo;
    insertCharacter = insertCharacterMongo;
}
else if (database === 'postgreSQL') {
    pg = require('pg');
    updateCoins = updateCoinsPostgres;
    displayCharacters = displayCharactersPostgres;
    displayOneCharacter = displayOneCharacterPostgres;
    insertCharacter = insertCharacterPostgres;
}
else {
    console.error('Unrecognized database ' + database);
    process.exit(1);
}

var rawTemplate = fs.readFileSync('templates/characters.html', 'utf8');
var charactersTemplate = handlebars.compile(rawTemplate);

var rawTemplate = fs.readFileSync('templates/oneChar.html', 'utf8');
var oneCharTemplate = handlebars.compile(rawTemplate);

// Define functions for interacting with the databases
function sendToMongoDB(res, callback) {
    MongoClient.connect('mongodb://localhost/test', function(err, db) {
        if(err) {
            res.writeHead(500, {'Content-Type': 'text/html'});
            res.end('coin update failed: database connection error');
            return console.error('could not connect to mongo', err);
        }
        callback(db);
    });
}

function updateCoinsMongo(res, name, coins) {
    sendToMongoDB(res, function(db) {
        var collection = db.collection('characters');
        collection.update(
            {name: name},
            {$set: {coins: coins}},
            function(err, statusObj) {
                if(err) {
                    res.writeHead(500, {'Content-Type': 'text/html'});
                    res.end('coin update failed: database error');
                    return console.error('error running query', err);
                }
                if (statusObj.result.nModified != 1) {
                    res.writeHead(500, {'Content-Type': 'text/html'});
                    res.end('coin update failed: ' + statusObj.result.nModified + ' documents updated');
                    return console.error('Expected 1 document updated, but ' +
                                           statusObj.result.nModified + ' were: ' + statusObj);
                }
                res.writeHead(200, {'Content-Type': 'text/html'});
                res.end('coin update received');
            }
        );
    });
}

function displayCharactersMongo(res) {
    sendToMongoDB(res, function (db) {
        var collection = db.collection('characters');
        collection.find({}).toArray(function(err, docs) {
            if(err) {
                res.writeHead(500, {'Content-Type': 'text/html'});
                res.end('cannot display characters: database error');
                return console.error('error running query', err);
            }
            var context = {'characters': docs};
            var html = charactersTemplate(context);
            db.close();
            res.writeHead(200, {'Content-Type': 'text/html'});
            res.end(html);
        });
    });
}

function displayOneCharacterMongo(res, name) {
    sendToMongoDB(res, function(db) {
        var collection = db.collection('characters');
        collection.find({name: name}).toArray(function (err, docs) {
            if (err) {
                res.writeHead(500, {'Content-Type': 'text/html'});
                res.end('cannot display characters: database error');
                return console.error('error running query', err);
            }
            if (docs.length != 1) {
                res.writeHead(500, {'Content-Type': 'text/html'});
                res.end('Expected 1 result but got ' + docs.length);
                return console.error('Expected 1 result but got ' + docs.length);
            }
            var context = docs[0];
            var html = oneCharTemplate(context);
            db.close();
            res.writeHead(200, {'Content-Type': 'text/html'});
            res.end(html);
        });
    });
}

function insertCharacterMongo(res, name, street_address, kingdom) {
    sendToMongoDB(res, function (db) {
        var collection = db.collection('characters');
        collection.insert({
             _id: sanitize(name),
             name: sanitize(name),
             location: { street_address: sanitize(street_address), kingdom: sanitize(kingdom) },
             coins: 0,
             lives: 0,
             friends: [],
             enemies: [],
        });
        db.close();
        // dislay the characters page
        res.writeHead(301, {Location: '/'});
        res.end();
    });
}

function sendToPostgresDB(res, callback) {
    pg.connect('postgres://localhost/mario_example', function(err, client) {
        if(err) {
            res.writeHead(500, {'Content-Type': 'text/html'});
            res.end('coin update failed: database connection error');
            return console.error('could not connect to postgres', err);
        }
        callback(client);
    });
}

function updateCoinsPostgres(res, name, coins) {
    sendToPostgresDB(res, function(client) {
        // https://github.com/brianc/node-postgres/wiki/Example
        client.query(
            'UPDATE characters SET coins=$1 WHERE name=$2',
            [coins, name],
            function(err, result) {
                if (err) {
                    res.writeHead(500, {'Content-Type': 'text/html'});
                    res.end('coin update failed: database error');
                    return console.error('error running query', err);
                }
                res.writeHead(200, {'Content-Type': 'text/html'});
                res.end('coin update received');
            }
        );
    });
}

function displayCharactersPostgres(res) {
    sendToPostgresDB(res, function(client) {
        client.query('SELECT * FROM characters', function(err, result) {
            if (err) {
                res.writeHead(500, {'Content-Type': 'text/html'});
                res.end('database error');
                return console.error('error running query', err);
            }
            var context = {'characters': result.rows};
            var html = charactersTemplate(context);
            client.end();
            res.writeHead(200, {'Content-Type': 'text/html'});
            res.end(html);
        });
    });
}

function displayOneCharacterPostgres(res, name) {
    sendToPostgresDB(res, function (client) {
        client.query(
            'SELECT * FROM characters WHERE name=$1',
            [name],
            function(err, result) {
                if (err) {
                    res.writeHead(500, {'Content-Type': 'text/html'});
                    res.end('database error');
                    return console.error('error running query', err);
                }
                if (result.rows.length != 1) {
                    res.writeHead(500, {'Content-Type': 'text/html'});
                    res.end('Expected 1 result but got ' + rows.length);
                    return console.error('Expected 1 result but got ' + rows.length);
                }
                var context = result.rows[0];
                var html = oneCharTemplate(context);
                client.end();
                res.writeHead(200, {'Content-Type': 'text/html'});
                res.end(html);
            }
        );
    });
}

function insertCharacterPostgres(res, name, street_address, kingdom) {
    sendToPostgresDB(res, function (client) {
        client.query(
            'INSERT INTO characters (name, street_address, kingdom, coins, lives) VALUES ($1, $2, $3, 0, 0)',
            [name, street_address, kingdom],
            function (err, result) {
                if (err) {
                    res.writeHead(500, {'Content-Type': 'text/html'});
                    res.end('database error');
                    return console.error('error running query', err);
                }
                // dislay the characters page
                res.writeHead(301, {Location: '/'});
                res.end();
            }
        );
    });
}

function handlePostData(req, res, callback) {
    var body = '';
    // http://stackoverflow.com/questions/4295782/how-do-you-extract-post-data-in-node-js
    // collect all of the POST data
    req.on('data', function (data) {
        body += data;
        // Too much POST data, kill the connection!
        if (body.length > 1e6) {
            request.connection.destroy();
            res.writeHead(413, {'Content-Type': 'text/html'});
            res.end('coin update failed: POST data too large');
            return console.error('POST data too large');
        }
    });
    // when the data collection is done, update the database with the new information
    req.on('end', function () {
        callback(res, body);
    });
}

handlebars.registerHelper('replaceSpaces', function(name) {
    var newName = name.replace(/ /g, '_');
    return new handlebars.SafeString(newName);
});

var server = http.createServer(function (req, res) {
    // first handle the CSS and JavaScript files
    if (req.url === '/static/appStyle.css') {
        res.writeHead(200, {'Content-Type': 'text/css'});
        res.end(fs.readFileSync('static/appStyle.css', 'utf8'));
    }
    else if (req.url === '/static/appUtils.js') {
        res.writeHead(200, {'Content-Type': 'text/javascript'});
        res.end(fs.readFileSync('static/appUtils.js', 'utf8'));
    }
    // this should contain the new coin data, so ignore a request to this URL that isn't a POST
    else if (req.url === '/setcoin'  && req.method === 'POST') {
        handlePostData(req, res, function (res, body) {
            var post = JSON.parse(body);
            var name = post['name'];
            var coins = post['coins'];
            updateCoins(res, name, coins);
        });
    }
    else if (req.url.startsWith('/show')) {
        var name = req.url.substring(5, req.url.length).replace(/_/g, ' ');
        displayOneCharacter(res, name);
    }
    // this should contain the new coin data, so ignore a request to this URL that isn't a POST
    else if (req.url.startsWith("/addNewCharacter") && req.method === 'POST') {
        handlePostData(req, res, function (res, body) {
            var post = qs.parse(body);
            var name = post['charName'];
            var street_address = post['street_address'];
            var kingdom = post['kingdom'];
            insertCharacter(res, name, street_address, kingdom);
        });
    }
    else {
        // for any other URLs, just display all characters currently in the database
        displayCharacters(res);
    }

});

server.listen(3000);
<commit_msg>Be consistent about quotation marks<commit_after>var http = require('http');
var fs = require('fs');
var handlebars = require('handlebars');
var qs = require('querystring');

// setup the database functions based on which database is being used
var database = 'mongoDB'; // change to postgreSQL to use postgres instead
var MongoClient = null;
var sanitize = null;
var pg = null;
var updateCoins = null;
var displayCharacters = null;
var displayOneCharacter = null;
var insertCharacter = null;
if (database === 'mongoDB') {
    MongoClient = require('mongodb').MongoClient;
    sanitize = require('mongo-sanitize');
    updateCoins = updateCoinsMongo;
    displayCharacters = displayCharactersMongo;
    displayOneCharacter = displayOneCharacterMongo;
    insertCharacter = insertCharacterMongo;
}
else if (database === 'postgreSQL') {
    pg = require('pg');
    updateCoins = updateCoinsPostgres;
    displayCharacters = displayCharactersPostgres;
    displayOneCharacter = displayOneCharacterPostgres;
    insertCharacter = insertCharacterPostgres;
}
else {
    console.error('Unrecognized database ' + database);
    process.exit(1);
}

var rawTemplate = fs.readFileSync('templates/characters.html', 'utf8');
var charactersTemplate = handlebars.compile(rawTemplate);

var rawTemplate = fs.readFileSync('templates/oneChar.html', 'utf8');
var oneCharTemplate = handlebars.compile(rawTemplate);

// Define functions for interacting with the databases
function sendToMongoDB(res, callback) {
    MongoClient.connect('mongodb://localhost/test', function(err, db) {
        if(err) {
            res.writeHead(500, {'Content-Type': 'text/html'});
            res.end('coin update failed: database connection error');
            return console.error('could not connect to mongo', err);
        }
        callback(db);
    });
}

function updateCoinsMongo(res, name, coins) {
    sendToMongoDB(res, function(db) {
        var collection = db.collection('characters');
        collection.update(
            {name: name},
            {$set: {coins: coins}},
            function(err, statusObj) {
                if(err) {
                    res.writeHead(500, {'Content-Type': 'text/html'});
                    res.end('coin update failed: database error');
                    return console.error('error running query', err);
                }
                if (statusObj.result.nModified != 1) {
                    res.writeHead(500, {'Content-Type': 'text/html'});
                    res.end('coin update failed: ' + statusObj.result.nModified + ' documents updated');
                    return console.error('Expected 1 document updated, but ' +
                                           statusObj.result.nModified + ' were: ' + statusObj);
                }
                res.writeHead(200, {'Content-Type': 'text/html'});
                res.end('coin update received');
            }
        );
    });
}

function displayCharactersMongo(res) {
    sendToMongoDB(res, function (db) {
        var collection = db.collection('characters');
        collection.find({}).toArray(function(err, docs) {
            if(err) {
                res.writeHead(500, {'Content-Type': 'text/html'});
                res.end('cannot display characters: database error');
                return console.error('error running query', err);
            }
            var context = {'characters': docs};
            var html = charactersTemplate(context);
            db.close();
            res.writeHead(200, {'Content-Type': 'text/html'});
            res.end(html);
        });
    });
}

function displayOneCharacterMongo(res, name) {
    sendToMongoDB(res, function(db) {
        var collection = db.collection('characters');
        collection.find({name: name}).toArray(function (err, docs) {
            if (err) {
                res.writeHead(500, {'Content-Type': 'text/html'});
                res.end('cannot display characters: database error');
                return console.error('error running query', err);
            }
            if (docs.length != 1) {
                res.writeHead(500, {'Content-Type': 'text/html'});
                res.end('Expected 1 result but got ' + docs.length);
                return console.error('Expected 1 result but got ' + docs.length);
            }
            var context = docs[0];
            var html = oneCharTemplate(context);
            db.close();
            res.writeHead(200, {'Content-Type': 'text/html'});
            res.end(html);
        });
    });
}

function insertCharacterMongo(res, name, street_address, kingdom) {
    sendToMongoDB(res, function (db) {
        var collection = db.collection('characters');
        collection.insert({
             _id: sanitize(name),
             name: sanitize(name),
             location: { street_address: sanitize(street_address), kingdom: sanitize(kingdom) },
             coins: 0,
             lives: 0,
             friends: [],
             enemies: [],
        });
        db.close();
        // dislay the characters page
        res.writeHead(301, {Location: '/'});
        res.end();
    });
}

function sendToPostgresDB(res, callback) {
    pg.connect('postgres://localhost/mario_example', function(err, client) {
        if(err) {
            res.writeHead(500, {'Content-Type': 'text/html'});
            res.end('coin update failed: database connection error');
            return console.error('could not connect to postgres', err);
        }
        callback(client);
    });
}

function updateCoinsPostgres(res, name, coins) {
    sendToPostgresDB(res, function(client) {
        // https://github.com/brianc/node-postgres/wiki/Example
        client.query(
            'UPDATE characters SET coins=$1 WHERE name=$2',
            [coins, name],
            function(err, result) {
                if (err) {
                    res.writeHead(500, {'Content-Type': 'text/html'});
                    res.end('coin update failed: database error');
                    return console.error('error running query', err);
                }
                res.writeHead(200, {'Content-Type': 'text/html'});
                res.end('coin update received');
            }
        );
    });
}

function displayCharactersPostgres(res) {
    sendToPostgresDB(res, function(client) {
        client.query('SELECT * FROM characters', function(err, result) {
            if (err) {
                res.writeHead(500, {'Content-Type': 'text/html'});
                res.end('database error');
                return console.error('error running query', err);
            }
            var context = {'characters': result.rows};
            var html = charactersTemplate(context);
            client.end();
            res.writeHead(200, {'Content-Type': 'text/html'});
            res.end(html);
        });
    });
}

function displayOneCharacterPostgres(res, name) {
    sendToPostgresDB(res, function (client) {
        client.query(
            'SELECT * FROM characters WHERE name=$1',
            [name],
            function(err, result) {
                if (err) {
                    res.writeHead(500, {'Content-Type': 'text/html'});
                    res.end('database error');
                    return console.error('error running query', err);
                }
                if (result.rows.length != 1) {
                    res.writeHead(500, {'Content-Type': 'text/html'});
                    res.end('Expected 1 result but got ' + rows.length);
                    return console.error('Expected 1 result but got ' + rows.length);
                }
                var context = result.rows[0];
                var html = oneCharTemplate(context);
                client.end();
                res.writeHead(200, {'Content-Type': 'text/html'});
                res.end(html);
            }
        );
    });
}

function insertCharacterPostgres(res, name, street_address, kingdom) {
    sendToPostgresDB(res, function (client) {
        client.query(
            'INSERT INTO characters (name, street_address, kingdom, coins, lives) VALUES ($1, $2, $3, 0, 0)',
            [name, street_address, kingdom],
            function (err, result) {
                if (err) {
                    res.writeHead(500, {'Content-Type': 'text/html'});
                    res.end('database error');
                    return console.error('error running query', err);
                }
                // dislay the characters page
                res.writeHead(301, {Location: '/'});
                res.end();
            }
        );
    });
}

function handlePostData(req, res, callback) {
    var body = '';
    // http://stackoverflow.com/questions/4295782/how-do-you-extract-post-data-in-node-js
    // collect all of the POST data
    req.on('data', function (data) {
        body += data;
        // Too much POST data, kill the connection!
        if (body.length > 1e6) {
            request.connection.destroy();
            res.writeHead(413, {'Content-Type': 'text/html'});
            res.end('coin update failed: POST data too large');
            return console.error('POST data too large');
        }
    });
    // when the data collection is done, update the database with the new information
    req.on('end', function () {
        callback(res, body);
    });
}

handlebars.registerHelper('replaceSpaces', function(name) {
    var newName = name.replace(/ /g, '_');
    return new handlebars.SafeString(newName);
});

var server = http.createServer(function (req, res) {
    // first handle the CSS and JavaScript files
    if (req.url === '/static/appStyle.css') {
        res.writeHead(200, {'Content-Type': 'text/css'});
        res.end(fs.readFileSync('static/appStyle.css', 'utf8'));
    }
    else if (req.url === '/static/appUtils.js') {
        res.writeHead(200, {'Content-Type': 'text/javascript'});
        res.end(fs.readFileSync('static/appUtils.js', 'utf8'));
    }
    // this should contain the new coin data, so ignore a request to this URL that isn't a POST
    else if (req.url === '/setcoin'  && req.method === 'POST') {
        handlePostData(req, res, function (res, body) {
            var post = JSON.parse(body);
            var name = post['name'];
            var coins = post['coins'];
            updateCoins(res, name, coins);
        });
    }
    else if (req.url.startsWith('/show')) {
        var name = req.url.substring(5, req.url.length).replace(/_/g, ' ');
        displayOneCharacter(res, name);
    }
    // this should contain the new coin data, so ignore a request to this URL that isn't a POST
    else if (req.url.startsWith('/addNewCharacter') && req.method === 'POST') {
        handlePostData(req, res, function (res, body) {
            var post = qs.parse(body);
            var name = post['charName'];
            var street_address = post['street_address'];
            var kingdom = post['kingdom'];
            insertCharacter(res, name, street_address, kingdom);
        });
    }
    else {
        // for any other URLs, just display all characters currently in the database
        displayCharacters(res);
    }

});

server.listen(3000);
<|endoftext|>


<commit_before>//The store is an event emitter.
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
var {isArray, isEmpty, isObject, isFunction} = require('lodash/lang');
var {defer} = require('lodash/function');
var getEntityInformations = require('../definition/entity/builder').getEntityInformations;
var capitalize = require('lodash/string/capitalize');
var Immutable = require('immutable');
var AppDispatcher = require('../dispatcher');
/**
 * @class CoreStore
 */
class CoreStore extends EventEmitter {

  /**
   * Contructor of the store class.
   */
  constructor(config) {
    assign(this, {
      config
    });
    //Initialize the data as immutable map.
    this.data = Immutable.Map({});
    this.status = Immutable.Map({});
    this.error = Immutable.Map({});
    this.pendingEvents = [];
    this.customHandler = assign({}, config.customHandler);
    //Register all gernerated methods.
    this.buildDefinition();
    this.buildEachNodeChangeEventListener();
    this.registerDispatcher();
  }

  /**
   * Initialize the store configuration.
   * @param {object} storeConfiguration - The store configuration for the initialization.
   */
  buildDefinition() {
      /**
       * Build the definitions for the entity (may be a subject.)
       * @type {object}
       */
      this.definition = this.config.definition || getEntityInformations(
        this.config.definitionPath,
        this.config.customDefinition
      );
      return this.definition;
  }
  /** Return the status of a definition.
   * @returns {string} - The status of a definition.
   */
  getStatus(def){
    if (this.status.has(def)){
      return this.status.get(def);
    }
    return undefined;
  }
  /**
   * Emit all events pending in the pendingEvents map.
   */
  emitPendingEvents(){
    this.pendingEvents.map((evtToEmit)=>{
      this.emit(evtToEmit.name, evtToEmit.data);
    });
  }

  /**
   * Replace the emit function with a willEmit in otder to store the changing event but send it afterwards.
   * @param eventName {string} - The event name.
   * @param  data {object} - The event's associated data.
   */
  willEmit(eventName, data){
    this.pendingEvents.push({name: eventName, data: data});
  }

  /**
   * Clear all pending events.
   */
  clearPendingEvents(){
    this.pendingEvents = [];
  }
  /**
  * Build a change listener for each property in the definition. (should be macro entities);
  */
  buildEachNodeChangeEventListener() {
    var currentStore = this;
      //Loop through each store properties.
      for (var definition in this.definition) {
        var capitalizeDefinition = capitalize(definition);
        //Creates the change listener
        currentStore[`add${capitalizeDefinition}ChangeListener`] = function(def){
          return function (cb) {
            currentStore.addListener(`${def}:change`, cb);
        }}(definition);
        //Remove the change listener
        currentStore[`remove${capitalizeDefinition}ChangeListener`] = function(def){
          return function (cb) {
            currentStore.removeListener(`${def}:change`, cb);
        }}(definition);
        //Create an update method.
        if(currentStore[`update${capitalizeDefinition}`] === undefined){
          currentStore[`update${capitalizeDefinition}`] = function(def){
            return function (dataNode, status) {
              var immutableNode = isFunction(dataNode) ? dataNode : Immutable.fromJS(dataNode);
              currentStore.data = currentStore.data.set(def, immutableNode);
              //Update the status on the data.
              currentStore.status = currentStore.status.set(def, status);
              currentStore.willEmit(`${def}:change`, {property: def, status: status});
          }}(definition);
        }

        //Create a get method.
        if(currentStore[`get${capitalizeDefinition}`] === undefined){
          currentStore[`get${capitalizeDefinition}`] = function(def){
            return function () {
              var hasData = currentStore.data.has(def);
              if(hasData){
                var rawData = currentStore.data.get(def);
                //If the store node isn't an object, immutable solution are non sens.
                if(isFunction(rawData) || !isObject(rawData)){
                  return rawData;
                }
                else {
                  var data = rawData.toJS();
                  if(!isEmpty(data)){
                    return data;
                  }
                }
              }
              return undefined;
            };
          }(definition);
        }
        //Creates the error change listener
        currentStore[`add${capitalizeDefinition}ErrorListener`] = function(def){
            return function (cb) {
              currentStore.addListener(`${def}:error`, cb);
        }}(definition);
        //Remove the change listener
        currentStore[`remove${capitalizeDefinition}ErrorListener`] = function(def){
            return function (cb) {
              currentStore.removeListener(`${def}:error`, cb);
        }}(definition);
        //Create an update method.
        currentStore[`updateError${capitalizeDefinition}`] = function(def){
            return function (dataNode) {
              //CheckIsObject
              var immutableNode = Immutable[isArray(dataNode) ? "List" : "Map"](dataNode);
              currentStore.error = currentStore.error.set(def, immutableNode);
              currentStore.willEmit(`${def}:error`);
        }}(definition);
        //Create a get method.
        currentStore[`getError${capitalizeDefinition}`] = function(def){
            return function(){
              var hasData = currentStore.error.has(def);
              return hasData ? currentStore.error.get(def).toJS() : undefined;
            };
        }(definition);
      }
    }

  delayPendingEvents(context){
    //Delay all the change emit by the store to be sure it is done after the internal store propagation and to go out of the dispatch function.
    defer(()=>{
      context.emitPendingEvents();
      context.clearPendingEvents();
    });
  }
  /**
   * The store registrer itself on the dispatcher.
   */
  registerDispatcher(){
    var currentStore = this;
    this.dispatch = AppDispatcher.register(function(transferInfo) {

      //currentStore.clearPendingEvents();
      if(currentStore.globalCustomHandler){
        return currentStore.globalCustomHandler.call(currentStore, transferInfo);
      }

      //Read data from the action transfer information.
      var rawData = transferInfo.action.data;
      var status = transferInfo.action.status || {};
      var type = transferInfo.action.type;

      //Call each node handler for the matching definition's node.
      for(var node in rawData){
        if(currentStore.definition[node]){
          //Call a custom handler if this exists.
          if(currentStore.customHandler && currentStore.customHandler[node] && currentStore.customHandler[node][type]){
            currentStore.customHandler[node][type].call(currentStore, rawData[node], status[node]);
          }else {
            //Update the data for the given node. and emit the change/.
            currentStore[`${type}${capitalize(node)}`](rawData[node], status[node]);
          }
        }
      }
      currentStore.delayPendingEvents(currentStore);

    });
  }
    /**
     * Add a listener on a store event.
     * @param {string}   eventName - Event name.
     * @param {Function} cb - CallBack to call on the event change name.
     */
  addListener(eventName, cb) {
    this.on(eventName, cb);
  }

}
module.exports = CoreStore;
<commit_msg>[store] Add the _buildInformations method, from the transferInformation Data in order to propagate this object in the event.<commit_after>//The store is an event emitter.
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
var {isArray, isEmpty, isObject, isFunction} = require('lodash/lang');
var {defer} = require('lodash/function');
var getEntityInformations = require('../definition/entity/builder').getEntityInformations;
var capitalize = require('lodash/string/capitalize');
var Immutable = require('immutable');
var AppDispatcher = require('../dispatcher');
/**
 * @class CoreStore
 */
class CoreStore extends EventEmitter {

  /**
   * Contructor of the store class.
   */
  constructor(config) {
    assign(this, {
      config
    });
    //Initialize the data as immutable map.
    this.data = Immutable.Map({});
    this.status = Immutable.Map({});
    this.error = Immutable.Map({});
    this.callerId = undefined;
    this.pendingEvents = [];
    this.customHandler = assign({}, config.customHandler);
    //Register all gernerated methods.
    this.buildDefinition();
    this.buildEachNodeChangeEventListener();
    this.registerDispatcher();
  }

  /**
   * Initialize the store configuration.
   * @param {object} storeConfiguration - The store configuration for the initialization.
   */
  buildDefinition() {
      /**
       * Build the definitions for the entity (may be a subject.)
       * @type {object}
       */
      this.definition = this.config.definition || getEntityInformations(
        this.config.definitionPath,
        this.config.customDefinition
      );
      return this.definition;
  }
  /** Return the status of a definition.
   * @returns {string} - The status of a definition.
   */
  getStatus(def){
    if (this.status.has(def)){
      return this.status.get(def);
    }
    return undefined;
  }
  /**
   * Emit all events pending in the pendingEvents map.
   */
  emitPendingEvents(){
    this.pendingEvents.map((evtToEmit)=>{
      this.emit(evtToEmit.name, evtToEmit.data);
    });
  }

  /**
   * Replace the emit function with a willEmit in otder to store the changing event but send it afterwards.
   * @param eventName {string} - The event name.
   * @param  data {object} - The event's associated data.
   */
  willEmit(eventName, data){
    this.pendingEvents.push({name: eventName, data: data});
  }

  /**
   * Clear all pending events.
   */
  clearPendingEvents(){
    this.pendingEvents = [];
  }
  /**
  * Build a change listener for each property in the definition. (should be macro entities);
  */
  buildEachNodeChangeEventListener() {
    var currentStore = this;
      //Loop through each store properties.
      for (var definition in this.definition) {
        var capitalizeDefinition = capitalize(definition);
        //Creates the change listener
        currentStore[`add${capitalizeDefinition}ChangeListener`] = function(def){
          return function (cb) {
            currentStore.addListener(`${def}:change`, cb);
        }}(definition);
        //Remove the change listener
        currentStore[`remove${capitalizeDefinition}ChangeListener`] = function(def){
          return function (cb) {
            currentStore.removeListener(`${def}:change`, cb);
        }}(definition);
        //Create an update method.
        if(currentStore[`update${capitalizeDefinition}`] === undefined){
          currentStore[`update${capitalizeDefinition}`] = function(def){
            return function (dataNode, status, informations) {
              var immutableNode = isFunction(dataNode) ? dataNode : Immutable.fromJS(dataNode);
              currentStore.data = currentStore.data.set(def, immutableNode);
              //Update the status on the data.
              currentStore.status = currentStore.status.set(def, status);

              currentStore.willEmit(`${def}:change`, {property: def, status: status, informations: informations});
          }}(definition);
        }

        //Create a get method.
        if(currentStore[`get${capitalizeDefinition}`] === undefined){
          currentStore[`get${capitalizeDefinition}`] = function(def){
            return function () {
              var hasData = currentStore.data.has(def);
              if(hasData){
                var rawData = currentStore.data.get(def);
                //If the store node isn't an object, immutable solution are non sens.
                if(isFunction(rawData) || !isObject(rawData)){
                  return rawData;
                }
                else {
                  var data = rawData.toJS();
                  if(!isEmpty(data)){
                    return data;
                  }
                }
              }
              return undefined;
            };
          }(definition);
        }
        //Creates the error change listener
        currentStore[`add${capitalizeDefinition}ErrorListener`] = function(def){
            return function (cb) {
              currentStore.addListener(`${def}:error`, cb);
        }}(definition);
        //Remove the change listener
        currentStore[`remove${capitalizeDefinition}ErrorListener`] = function(def){
            return function (cb) {
              currentStore.removeListener(`${def}:error`, cb);
        }}(definition);
        //Create an update method.
        currentStore[`updateError${capitalizeDefinition}`] = function(def){
            return function (dataNode) {
              //CheckIsObject
              var immutableNode = Immutable[isArray(dataNode) ? "List" : "Map"](dataNode);
              currentStore.error = currentStore.error.set(def, immutableNode);
              currentStore.willEmit(`${def}:error`);
        }}(definition);
        //Create a get method.
        currentStore[`getError${capitalizeDefinition}`] = function(def){
            return function(){
              var hasData = currentStore.error.has(def);
              return hasData ? currentStore.error.get(def).toJS() : undefined;
            };
        }(definition);
      }
    }

  delayPendingEvents(context){
    //Delay all the change emit by the store to be sure it is done after the internal store propagation and to go out of the dispatch function.
    defer(()=>{
      context.emitPendingEvents();
      context.clearPendingEvents();
    });
  }
  _buildInformations(incomingInfos){
    return {
      callerId : incomingInfos.action.callerId
    }
  }
  /**
   * The store registrer itself on the dispatcher.
   */
  registerDispatcher(){
    var currentStore = this;
    this.dispatch = AppDispatcher.register(function(transferInfo) {

      //currentStore.clearPendingEvents();
      if(currentStore.globalCustomHandler){
        return currentStore.globalCustomHandler.call(currentStore, transferInfo);
      }

      //Read data from the action transfer information.
      var rawData = transferInfo.action.data;
      var status = transferInfo.action.status || {};
      var type = transferInfo.action.type;
      var otherInformations = this._buildInformations(transferInfo);

      //Call each node handler for the matching definition's node.
      for(var node in rawData){
        if(currentStore.definition[node]){
          //Call a custom handler if this exists.
          if(currentStore.customHandler && currentStore.customHandler[node] && currentStore.customHandler[node][type]){
            currentStore.customHandler[node][type].call(currentStore, rawData[node], status[node], otherInformations);
          }else {
            //Update the data for the given node. and emit the change/.
            currentStore[`${type}${capitalize(node)}`](rawData[node], status[node], );
          }
        }
      }
      currentStore.delayPendingEvents(currentStore);
    });
  }
    /**
     * Add a listener on a store event.
     * @param {string}   eventName - Event name.
     * @param {Function} cb - CallBack to call on the event change name.
     */
  addListener(eventName, cb) {
    this.on(eventName, cb);
  }

}
module.exports = CoreStore;
<|endoftext|>


<commit_before>var url = require('url')
var http = require('http')
var https = require('https')

var HTTP = {}
var METHODS = ['get', 'post', 'patch', 'put', 'delete']

METHODS.forEach(function (method) {
  // https://nodejs.org/api/http.html#http_http_request_options_callback
  HTTP[method] = function (options, data) {
    return new Promise(function (resolve, reject) {
      if (data) {
        var isObject = typeof data === 'object'
        var headers = options.headers || (options.headers = {})
        if (!headers['Content-Type']) {
          headers['Content-Type'] = isObject ? 'application/json' : 'application/x-www-form-urlencoded'
        }
        if (isObject) {
          data = JSON.stringify(data)
        }
        headers['Content-Length'] = data.length
      }
      if (typeof options === 'string') {
        options = url.parse(options)
      }
      options.method = method
      var module = options.protocol.replace(':', '') === 'https' ? https : http
      var req = module.request(options, function (response) {
        var data = ''
        response.setEncoding('utf8')
        response.on('data', function (chunk) {
          data += chunk
        })
        response.on('end', function () {
          resolve({
            data: data,
            response: response
          })
        })
      }).on('error', function (err) {
        reject(err)
      })
      if (data) {
        req.write(data)
      }
      req.end()
    })
  }
})

HTTP.json = function (options) {
  return this.get(options).then(function (result) {
    try {
      return JSON.parse(result.data)
    } catch (e) {
      return Promise.reject(e)
    }
  })
}

module.exports = HTTP
<commit_msg>Delete http.js<commit_after><|endoftext|>


<commit_before>var Server = require('./server')
var EventEmitter = require('events').EventEmitter
var async = require('async')

function App(config){
  this.config = config
  this.start()
}

App.prototype = {
  __proto__: EventEmitter.prototype,
  start: function(){
    this.setupServer()
  },
  setupServer: function(){
    this.server = new Server(this.config)
    this.server.start()
    this.server.once('server-start', function(){
      this.runTheTests()
    }.bind(this))
  },
  runTheTests: function(){
    var self = this
    var server = this.server
    this.config.getLaunchers(function(launchers){
      async.forEachSeries(launchers, function(launcher, next){
        if (launcher.protocol() === 'process'){
          new ProcessTestRunner(launcher, next).start()
        }else if (launcher.protocol() === 'browser'){
          new BrowserTestRunner(launcher, server, next).start()
        }else if (launcher.protocol() === 'tap'){
          new TapProcessTestRunner(launcher, next).start()
        }
      }, function(){
        process.exit()
      })
    })
  }
}

function BrowserTestRunner(launcher, server, onFinish){
  this.launcher = launcher
  this.server = server
  this.onFinish = onFinish
}
BrowserTestRunner.prototype = {
  start: function(){
    var launcher = this.launcher
    launcher.start()
    this.server.once('browser-login', this.onBrowserLogin.bind(this))
  },
  onBrowserLogin: function(browser, socket){
    socket.on('test-result', this.onTestResult.bind(this))
    socket.once('all-test-results', this.onAllTestResults.bind(this))
  },
  onTestResult: function(result){
    console.log((result.passed ? 'ok ' : 'not ok ') +
      result.id + ' ' + result.name)
  },
  onAllTestResults: function(results){
    var onFinish = this.onFinish
    //console.log('Got all test results', results)
    this.launcher.kill(null, function(){
      onFinish()
    })
  }
}

function ProcessTestRunner(launcher, onFinish){
  this.launcher = launcher
  this.onFinish = onFinish
}
ProcessTestRunner.prototype = {
  start: function(){
    this.launcher.start()
    this.launcher.once('processExit', this.onProcessExit.bind(this))
  },
  onProcessExit: function(code){
    if (code === 0){
      console.log('Test passed')
    }else{
      console.log('Test failed')
    }
    this.onFinish()
  }
}

var tap = require('tap')
function TapProcessTestRunner(launcher, onFinish){
  this.launcher = launcher
  this.onFinish = onFinish
  this.tapConsumer = new tap.Consumer()
  this.reporter = new TestReporter()
}
TapProcessTestRunner.prototype = {
  start: function(){
    this.launcher.start()
    this.launcher.process.stdout.pipe(this.tapConsumer)
    this.tapConsumer.on('data', this.onData.bind(this))
    this.tapConsumer.on('end', this.onEnd.bind(this))
    this.tapConsumer.on('bailout', this.onBailout.bind(this))
  },
  onData: function(data){
    if (typeof data === 'object'){
      this.reporter.report(data)
    }
  },
  onEnd: function(err, count){
    this.wrapUp()
  },
  onBailout: function(){
    this.wrapUp()
  },
  wrapUp: function(){
    this.reporter.finish()
    this.onFinish()
  }
}

function TestReporter(){
  this.total = 0
  this.pass = 0
}
TestReporter.prototype = {
  report: function(data){
    console.log((data.ok ? 'ok ' : 'not ok ') + data.id + ' ' + data.name)
      this.total++
      if (data.ok) this.pass++
  },
  finish: function(){
    console.log()
    console.log('1..' + this.total)
    console.log('# tests ' + this.total)
    console.log('# pass  ' + this.pass)
    console.log('# fail  ' + (this.total - this.pass))
  }
}

module.exports = App<commit_msg>Refactor.<commit_after>var Server = require('./server')
var EventEmitter = require('events').EventEmitter
var async = require('async')

function App(config){
  this.config = config
  this.start()
}

App.prototype = {
  __proto__: EventEmitter.prototype,
  start: function(){
    this.setupServer()
  },
  setupServer: function(){
    this.server = new Server(this.config)
    this.server.start()
    this.server.once('server-start', function(){
      this.runTheTests()
    }.bind(this))
  },
  runTheTests: function(){
    var self = this
    var server = this.server
    this.config.getLaunchers(function(launchers){
      async.forEachSeries(launchers, function(launcher, next){
        self.createTestRunner(launcher, next).start()
      }, function(){
        process.exit()
      })
    })
  },
  createTestRunner: function(launcher, next){
    if (launcher.protocol() === 'process'){
      return new ProcessTestRunner(launcher, next)
    }else if (launcher.protocol() === 'browser'){
      return new BrowserTestRunner(launcher, this.server, next)
    }else if (launcher.protocol() === 'tap'){
      return new TapProcessTestRunner(launcher, next)
    }
  }
}

function BrowserTestRunner(launcher, server, onFinish){
  this.launcher = launcher
  this.server = server
  this.onFinish = onFinish
}
BrowserTestRunner.prototype = {
  start: function(){
    var launcher = this.launcher
    launcher.start()
    this.server.once('browser-login', this.onBrowserLogin.bind(this))
  },
  onBrowserLogin: function(browser, socket){
    socket.on('test-result', this.onTestResult.bind(this))
    socket.once('all-test-results', this.onAllTestResults.bind(this))
  },
  onTestResult: function(result){
    console.log((result.passed ? 'ok ' : 'not ok ') +
      result.id + ' ' + result.name)
  },
  onAllTestResults: function(results){
    var onFinish = this.onFinish
    //console.log('Got all test results', results)
    this.launcher.kill(null, function(){
      onFinish()
    })
  }
}

function ProcessTestRunner(launcher, onFinish){
  this.launcher = launcher
  this.onFinish = onFinish
}
ProcessTestRunner.prototype = {
  start: function(){
    this.launcher.start()
    this.launcher.once('processExit', this.onProcessExit.bind(this))
  },
  onProcessExit: function(code){
    if (code === 0){
      console.log('Test passed')
    }else{
      console.log('Test failed')
    }
    this.onFinish()
  }
}

var tap = require('tap')
function TapProcessTestRunner(launcher, onFinish){
  this.launcher = launcher
  this.onFinish = onFinish
  this.tapConsumer = new tap.Consumer()
  this.reporter = new TestReporter()
}
TapProcessTestRunner.prototype = {
  start: function(){
    this.launcher.start()
    this.launcher.process.stdout.pipe(this.tapConsumer)
    this.tapConsumer.on('data', this.onData.bind(this))
    this.tapConsumer.on('end', this.onEnd.bind(this))
    this.tapConsumer.on('bailout', this.onBailout.bind(this))
  },
  onData: function(data){
    if (typeof data === 'object'){
      this.reporter.report(data)
    }
  },
  onEnd: function(err, count){
    this.wrapUp()
  },
  onBailout: function(){
    this.wrapUp()
  },
  wrapUp: function(){
    this.reporter.finish()
    this.onFinish()
  }
}

function TestReporter(){
  this.total = 0
  this.pass = 0
}
TestReporter.prototype = {
  report: function(data){
    console.log((data.ok ? 'ok ' : 'not ok ') + data.id + ' ' + data.name)
      this.total++
      if (data.ok) this.pass++
  },
  finish: function(){
    console.log()
    console.log('1..' + this.total)
    console.log('# tests ' + this.total)
    console.log('# pass  ' + this.pass)
    console.log('# fail  ' + (this.total - this.pass))
  }
}

module.exports = App<|endoftext|>


<commit_before>(function(jQuery) {
"use strict";

var $ = jQuery;

if (Echo.App.isDefined("Echo.Apps.NewsFeed")) return;

var newsFeed = Echo.App.manifest("Echo.Apps.NewsFeed");

newsFeed.config = {
	"tabs": {
		"official": {
			"enabled": true,
			"label": "Official"
		}
	},
	"content": {
		"section": "local",
		"tags": "",
		"officialSources":""
	},
	"presentation": {
		"displayUsernames": true,
		"displayAvatars": true
	},
	"replies": {
		"displayTweets": false,
		"displayNativeReplies": "all"
	},
	"auth": {
		"allowAnonymousSubmission": true
	},
	"dependencies": {
		"FilePicker": {"apiKey": undefined},
		"embedly": {"apiKey": undefined},
		"Janrain": {"appId": undefined},
		"StreamServer": {"appkey": undefined}
	},
	"postComposerActivatingTabs": [ "all" ],
	"advanced": {}
};

newsFeed.vars = {
	"contentTabs": [{
		"id": "official",
		"active": true,
		"visible": true,
		"type": "tab",
		"label": "Official"
	}, {
		"id": "top",
		"active": false,
		"visible": false,
		"type": "tab",
		"label": "Top Contributors"
	}, {
		"id": "all",
		"active": false,
		"visible": false,
		"type": "tab",
		"label": "All Contributors"
	}],
	"queries": undefined
};

newsFeed.dependencies = [{
	"url": "{config:cdnBaseURL.sdk}/gui.pack.css"
}, {
	"url": "//cdn.echoenabled.com/apps/echo/conversations/v2/app.js",
	"control": "Echo.Apps.Conversations"
}, {
	"url": "{config:cdnBaseURL.sdk}/streamserver.pack.js",
	"control": "Echo.StreamServer.Controls.Stream"
}];

newsFeed.templates.main =
	'
' +
		'

' +
		'

' +
	'
';

newsFeed.templates.tabs = {};

newsFeed.templates.tabs.nav =
	'
';

newsFeed.templates.tabs.navItem =
	'
' +
		'{data:label}' +
	'
';

newsFeed.renderers.postsTabs = function(element, extra) {
	var self = this;
	var tpls = this.templates.tabs;
	var tabs = this._initContentTabs();
	var nav = $(this.substitute({"template": tpls.nav}));
	var visibleTabsCounter = 0;
	$.map(tabs, function(tab) {
		if (tab.visible) {
			visibleTabsCounter++;
			var container = $(self.substitute({
				"template": tpls.navItem,
				"data": {
					"tabId": tab.id,
					"type": tab.type,
					"class": tab.active ? "active": "",
					"label": tab.label
				}
			}));
			container.on('show', function (e) {
				var conversationsStream = self.getComponent("Conversations");
				conversationsStream.config.set("allPosts.queryOverride", self._getQueryByActiveTabId(e.target.id));
				//TODO: optimize it asap
				if (self.config.get("postComposerActivatingTabs").indexOf(e.target.id) >= 0) {
					conversationsStream.config.set("postComposer.visible", true);
				} else {
					conversationsStream.config.set("postComposer.visible", false);
				}
				conversationsStream.refresh();
			});
			nav.append(container);
		}
	});
	if (visibleTabsCounter <= 1) {
		return element.empty();
	}
	return element.empty().append(nav);
};

newsFeed.renderers.newsFeed = function(element) {
	var query = this._getQueryByActiveTabId("official");
	this.initComponent({
		"id": "Conversations",
		"component": "Echo.Apps.Conversations",
		"config": $.extend(true, {
			"target": element,
			"targetURL": this.config.get("targetURL"),
			"postComposer": {
				"visible": false
			},
			"topPosts": {
				"visible": false
			},
			"allPosts": {
				"queryOverride": query,
				"displayCounter": false,
				"plugins": [{
					"name": "CardUITuner",
					"presentation": {
						"displayUsernames": this.config.get("presentation.displayUsernames"),
						"displayAvatars": this.config.get("presentation.displayAvatars")
					}
				}],
				"visible": true
			},
			"dependencies": this.config.get("dependencies")
		}, this.config.get("advanced"))
	});
	return element;
};

newsFeed.methods._initContentTabs = function() {
	var tabs = this.config.get("tabs");
	var result = [];
	$.map(this.get("contentTabs"), function(tab) {
		if (tabs && tabs[tab.id]) {
			if (tabs[tab.id] && tabs[tab.id].enabled === false) {
				tab.visible = false;
			}
			if (tabs[tab.id] && tabs[tab.id].label) {
				tab.label = tabs[tab.id].label;
			}
		}
		result.push(tab);
	});
	return result;
};

newsFeed.methods._getQueryByActiveTabId = function(activeTabId) {
	var queries = this.get("queries");
	if (!queries) {
		queries = this._formatQueries();
	}
	if (queries[activeTabId]) {
		return queries[activeTabId];
	} else {
		return queries.official;
	}
};

newsFeed.methods._formatQueries = function() {
	var contentParams = this.config.get("content");
	var commonQueryParts = [];

	if (contentParams.section && contentParams.section.toLowerCase() !== "local") {
		commonQueryParts.push("childrenof:" + contentParams.section);
	} else {
		var pageURL = $("link[rel='canonical']").attr('href');
		if(!pageURL || !Echo.Utils.parseURL(pageURL).domain) {
			pageURL =  document.location.href.split("#")[0];
		}
		commonQueryParts.push("childrenof:" + pageURL);
	}
	if (contentParams.officialSources && contentParams.officialSources.length > 0) {
		var sources = $.map(contentParams.officialSources.replace(" ", "").split(","), function(item) {
			return Echo.Utils.capitalize(item);
		});
		commonQueryParts.push("source:" + sources.join(","));
	}
	if (contentParams.tags && contentParams.tags.length > 0) {
		commonQueryParts.push("tags:" + contentParams.tags.toLowerCase().replace(" ", ""));
	}

	commonQueryParts.push("-state:ModeratorDeleted");
	var displayTweets = this.config.get("replies.displayTweets") || false;
	var nativeSubmissionsMode = this.config.get("replies.displayNativeReplies") || "all";

	commonQueryParts.push(this._formatChildrenQueryPart(displayTweets, nativeSubmissionsMode));
	commonQueryParts.push("safeHTML:permissive");

	return {
		"official": commonQueryParts.join(" "),
		"top": commonQueryParts.join(" "),
		"all": commonQueryParts.join(" ")
	};
};

newsFeed.methods._formatChildrenQueryPart = function(displayTweets, nativeSubmissionsMode) {
	var childrenQueryPart = "children:2 -state:ModeratorDeleted";
	if (nativeSubmissionsMode === "none") {
		if (!displayTweets) {
			childrenQueryPart = "children:0";
		} else {
			childrenQueryPart += " source:Twitter";
		}
	} else if (nativeSubmissionsMode === "top") {
		if (!displayTweets) {
			childrenQueryPart += " -source:Twitter";
		} else {
			childrenQueryPart += " source:Twitter";
		}
		childrenQueryPart += " OR markers:Conversations.TopPost";
	} else if (nativeSubmissionsMode === "all") {
		if (!displayTweets) {
			childrenQueryPart += " -source:Twitter";
		}
	}
	return childrenQueryPart;
};

newsFeed.css =
	'.{class:postsTabs} ul.nav { width: 100%; margin:0; }' +
	'.{class:postsTabs} ul.nav li { display: table-cell; width: 1%; float: none; }' +
	'.{class:postsTabs} ul.nav li a { text-align: center; border: 1px solid #CCC6C6; border-left: 0px; border-radius: 0px; margin: 2px 0px 2px 0px; }' +
	'.{class:postsTabs} ul.nav li:first-child a { border-left: 1px solid #CCC6C6; }' +
	'.{class:newsFeed} .echo-apps-conversations-streamHeader { display: none; }';

Echo.App.create(newsFeed);

})(Echo.jQuery);
<commit_msg>adding soft-refresh functionality<commit_after>(function(jQuery) {
"use strict";

var $ = jQuery;

if (Echo.App.isDefined("Echo.Apps.NewsFeed")) return;

var newsFeed = Echo.App.manifest("Echo.Apps.NewsFeed");

newsFeed.config = {
	"tabs": {
		"official": {
			"enabled": true,
			"label": "Official"
		}
	},
	"content": {
		"section": "local",
		"tags": "",
		"officialSources":""
	},
	"presentation": {
		"displayUsernames": true,
		"displayAvatars": true
	},
	"replies": {
		"displayTweets": false,
		"displayNativeReplies": "all"
	},
	"auth": {
		"allowAnonymousSubmission": true
	},
	"dependencies": {
		"FilePicker": {"apiKey": undefined},
		"embedly": {"apiKey": undefined},
		"Janrain": {"appId": undefined},
		"StreamServer": {"appkey": undefined}
	},
	"postComposerActivatingTabs": [ "all" ],
	"refreshOnUserInvalidate": false,
	"advanced": {}
};

newsFeed.vars = {
	"contentTabs": [{
		"id": "official",
		"active": true,
		"visible": true,
		"type": "tab",
		"label": "Official"
	}, {
		"id": "top",
		"active": false,
		"visible": false,
		"type": "tab",
		"label": "Top Contributors"
	}, {
		"id": "all",
		"active": false,
		"visible": false,
		"type": "tab",
		"label": "All Contributors"
	}],
	"queries": undefined
};

newsFeed.dependencies = [{
	"url": "{config:cdnBaseURL.sdk}/gui.pack.css"
}, {
	"url": "//cdn.echoenabled.com/apps/echo/conversations/v2/app.js",
	"control": "Echo.Apps.Conversations"
}, {
	"url": "{config:cdnBaseURL.sdk}/streamserver.pack.js",
	"control": "Echo.StreamServer.Controls.Stream"
}];

newsFeed.templates.main =
	'
' +
		'

' +
		'

' +
	'
';

newsFeed.templates.tabs = {};

newsFeed.templates.tabs.nav =
	'
';

newsFeed.templates.tabs.navItem =
	'
' +
		'{data:label}' +
	'
';

newsFeed.renderers.postsTabs = function(element, extra) {
	var self = this;
	var tpls = this.templates.tabs;
	var tabs = this._initContentTabs();
	var nav = $(this.substitute({"template": tpls.nav}));
	var visibleTabsCounter = 0;
	$.map(tabs, function(tab) {
		if (tab.visible) {
			visibleTabsCounter++;
			var container = $(self.substitute({
				"template": tpls.navItem,
				"data": {
					"tabId": tab.id,
					"type": tab.type,
					"class": tab.active ? "active": "",
					"label": tab.label
				}
			}));
			container.on('show', function (e) {
				var conversationsStream = self.getComponent("Conversations");
				conversationsStream.config.set("allPosts.queryOverride", self._getQueryByActiveTabId(e.target.id));
				//TODO: optimize it asap
				if (self.config.get("postComposerActivatingTabs").indexOf(e.target.id) >= 0) {
					conversationsStream.config.set("postComposer.visible", true);
				} else {
					conversationsStream.config.set("postComposer.visible", false);
				}
				conversationsStream.refresh();
			});
			nav.append(container);
		}
	});
	if (visibleTabsCounter <= 1) {
		return element.empty();
	}
	return element.empty().append(nav);
};

newsFeed.renderers.newsFeed = function(element) {
	var query = this._getQueryByActiveTabId("official");
	this.initComponent({
		"id": "Conversations",
		"component": "Echo.Apps.Conversations",
		"config": $.extend(true, {
			"target": element,
			"targetURL": this.config.get("targetURL"),
			"postComposer": {
				"visible": false
			},
			"topPosts": {
				"visible": false
			},
			"allPosts": {
				"queryOverride": query,
				"displayCounter": false,
				"plugins": [{
					"name": "CardUITuner",
					"presentation": {
						"displayUsernames": this.config.get("presentation.displayUsernames"),
						"displayAvatars": this.config.get("presentation.displayAvatars")
					}
				}],
				"visible": true
			},
			"dependencies": this.config.get("dependencies")
		}, this.config.get("advanced"))
	});
	return element;
};

newsFeed.methods._initContentTabs = function() {
	var tabs = this.config.get("tabs");
	var result = [];
	$.map(this.get("contentTabs"), function(tab) {
		if (tabs && tabs[tab.id]) {
			if (tabs[tab.id] && tabs[tab.id].enabled === false) {
				tab.visible = false;
			}
			if (tabs[tab.id] && tabs[tab.id].label) {
				tab.label = tabs[tab.id].label;
			}
		}
		result.push(tab);
	});
	return result;
};

newsFeed.methods._getQueryByActiveTabId = function(activeTabId) {
	var queries = this.get("queries");
	if (!queries) {
		queries = this._formatQueries();
	}
	if (queries[activeTabId]) {
		return queries[activeTabId];
	} else {
		return queries.official;
	}
};

newsFeed.methods._formatQueries = function() {
	var contentParams = this.config.get("content");
	var commonQueryParts = [];

	if (contentParams.section && contentParams.section.toLowerCase() !== "local") {
		commonQueryParts.push("childrenof:" + contentParams.section);
	} else {
		var pageURL = $("link[rel='canonical']").attr('href');
		if(!pageURL || !Echo.Utils.parseURL(pageURL).domain) {
			pageURL =  document.location.href.split("#")[0];
		}
		commonQueryParts.push("childrenof:" + pageURL);
	}
	if (contentParams.officialSources && contentParams.officialSources.length > 0) {
		var sources = $.map(contentParams.officialSources.replace(" ", "").split(","), function(item) {
			return Echo.Utils.capitalize(item);
		});
		commonQueryParts.push("source:" + sources.join(","));
	}
	if (contentParams.tags && contentParams.tags.length > 0) {
		commonQueryParts.push("tags:" + contentParams.tags.toLowerCase().replace(" ", ""));
	}

	commonQueryParts.push("-state:ModeratorDeleted");
	var displayTweets = this.config.get("replies.displayTweets") || false;
	var nativeSubmissionsMode = this.config.get("replies.displayNativeReplies") || "all";

	commonQueryParts.push(this._formatChildrenQueryPart(displayTweets, nativeSubmissionsMode));
	commonQueryParts.push("safeHTML:permissive");

	return {
		"official": commonQueryParts.join(" "),
		"top": commonQueryParts.join(" "),
		"all": commonQueryParts.join(" ")
	};
};

newsFeed.methods._formatChildrenQueryPart = function(displayTweets, nativeSubmissionsMode) {
	var childrenQueryPart = "children:2 -state:ModeratorDeleted";
	if (nativeSubmissionsMode === "none") {
		if (!displayTweets) {
			childrenQueryPart = "children:0";
		} else {
			childrenQueryPart += " source:Twitter";
		}
	} else if (nativeSubmissionsMode === "top") {
		if (!displayTweets) {
			childrenQueryPart += " -source:Twitter";
		} else {
			childrenQueryPart += " source:Twitter";
		}
		childrenQueryPart += " OR markers:Conversations.TopPost";
	} else if (nativeSubmissionsMode === "all") {
		if (!displayTweets) {
			childrenQueryPart += " -source:Twitter";
		}
	}
	return childrenQueryPart;
};

newsFeed.css =
	'.{class:postsTabs} ul.nav { width: 100%; margin:0; }' +
	'.{class:postsTabs} ul.nav li { display: table-cell; width: 1%; float: none; }' +
	'.{class:postsTabs} ul.nav li a { text-align: center; border: 1px solid #CCC6C6; border-left: 0px; border-radius: 0px; margin: 2px 0px 2px 0px; }' +
	'.{class:postsTabs} ul.nav li:first-child a { border-left: 1px solid #CCC6C6; }' +
	'.{class:newsFeed} .echo-apps-conversations-streamHeader { display: none; }';

Echo.App.create(newsFeed);

})(Echo.jQuery);
<|endoftext|>


<commit_before>var http = require('http'),
    Router = require('node-simple-router'),
    router = Router()
    drive = require("drive-db").load();
    fs = require('fs');

drive.update("1C3lg4GdS5DGnnpCWP1KHf6FHxLuM1lR44w_AAfKXdZ4");

//Function Parse Cookie
function parseCookies (request) {
    var list = {},
        rc = request.headers.cookie;

    rc && rc.split(';').forEach(function( cookie ) {
        var parts = cookie.split('=');
        list[parts.shift().trim()] = decodeURI(parts.join('='));
    });

    return list;
}

router.get("/hello", function(request, response) {

    var lang = request.url.slice(-2)

    //Language
    if (lang == "pt") {
        var formulas = [
            ['inicio-frase','adjetivo-vazio', 'objeto-elogio', 'ingrediente'],
            ['inicio-frase', 'unidade', 'adjetivo-chefe', 'ingrediente'],
            ['inicio-frase', 'objeto-elogio', 'regional']
        ];
    } else {
        var formulas = [
            ['inicio-frase','adjetivo-vazio', 'ingrediente', 'objeto-elogio'],
            ['inicio-frase', 'adjetivo-chefe', 'ingrediente', 'unidade'],
            ['inicio-frase', 'regional', 'objeto-elogio']
        ];
    }

    var r1 = Math.floor(Math.random() * (formulas.length) );
    var expression = "";

    formulas[r1].forEach(function(item) {
        var wordArray = drive.find({ category: item });

        //Random Value array
        var r2 = Math.floor(Math.random() * (wordArray.length) );

        //Language
        if (lang == "pt") {
            expression = expression+wordArray[r2].word+" ";
        } else {
            expression = expression+wordArray[r2].worden+" ";
        }

    });

    response.end(expression);
});

router.get("/", function(request, response) {

    var cookies = parseCookies(request);
    var lang;

    //Language
    if (!cookies['lang']) {
        lang = request.headers["accept-language"].substr(0,2);
    } else {
        lang = cookies['lang'];
    }

    if (lang == "pt") {
        response.writeHead(301, {
            'Location': 'http://gourmetstrike.com/pt',
            'Set-Cookie': 'lang=pt'
        });
        response.end();
    } else {
        fs.readFile("public/index.html", function (err, html) {
            if (err) {
                throw err;
            }
            response.writeHeader(200, {"Content-Type": "text/html", 'Set-Cookie': 'lang=en'});
            response.write(html);
            response.end();
        });
    }
});

router.get("/pt", function(request, response) {

    var file = "index-pt.html";

    fs.readFile("public/"+file, function (err, html) {
        if (err) {
            throw err;
        }
        response.writeHeader(200, {"Content-Type": "text/html", 'Set-Cookie': 'lang=pt'});
        response.write(html);
        response.end();
    });
});

/*
router.get("/en", function(request, response) {

    var file = "index.html";

    fs.readFile("public/"+file, function (err, html) {
        if (err) {
            throw err;
        }
        response.writeHeader(200, {"Content-Type": "text/html"});
        response.write(html);
        response.end();
    });
}); */

var server = http.createServer(router);
server.listen(process.env.PORT || 80, function(){
  console.log('listening on', server.address().port);
});<commit_msg>robots route<commit_after>var http = require('http'),
    Router = require('node-simple-router'),
    router = Router()
    drive = require("drive-db").load();
    fs = require('fs');

drive.update("1C3lg4GdS5DGnnpCWP1KHf6FHxLuM1lR44w_AAfKXdZ4");

//Function Parse Cookie
function parseCookies (request) {
    var list = {},
        rc = request.headers.cookie;

    rc && rc.split(';').forEach(function( cookie ) {
        var parts = cookie.split('=');
        list[parts.shift().trim()] = decodeURI(parts.join('='));
    });

    return list;
}

router.get("/hello", function(request, response) {

    var lang = request.url.slice(-2)

    //Language
    if (lang == "pt") {
        var formulas = [
            ['inicio-frase','adjetivo-vazio', 'objeto-elogio', 'ingrediente'],
            ['inicio-frase', 'unidade', 'adjetivo-chefe', 'ingrediente'],
            ['inicio-frase', 'objeto-elogio', 'regional']
        ];
    } else {
        var formulas = [
            ['inicio-frase','adjetivo-vazio', 'ingrediente', 'objeto-elogio'],
            ['inicio-frase', 'adjetivo-chefe', 'ingrediente', 'unidade'],
            ['inicio-frase', 'regional', 'objeto-elogio']
        ];
    }

    var r1 = Math.floor(Math.random() * (formulas.length) );
    var expression = "";

    formulas[r1].forEach(function(item) {
        var wordArray = drive.find({ category: item });

        //Random Value array
        var r2 = Math.floor(Math.random() * (wordArray.length) );

        //Language
        if (lang == "pt") {
            expression = expression+wordArray[r2].word+" ";
        } else {
            expression = expression+wordArray[r2].worden+" ";
        }

    });

    response.end(expression);
});

router.get("/", function(request, response) {

    var cookies = parseCookies(request);
    var lang;

    //Language
    if (!cookies['lang']) {
        if (request.headers["accept-language"]) {
            lang = request.headers["accept-language"].substr(0,2);
        }
    } else {
        lang = cookies['lang'];
    }

    if (lang == "pt") {
        response.writeHead(301, {
            'Location': 'http://gourmetstrike.com/pt',
            'Set-Cookie': 'lang=pt'
        });
        response.end();
    } else {
        fs.readFile("public/index.html", function (err, html) {
            if (err) {
                throw err;
            }
            response.writeHeader(200, {"Content-Type": "text/html", 'Set-Cookie': 'lang=en'});
            response.write(html);
            response.end();
        });
    }
});

router.get("/pt", function(request, response) {

    var file = "index-pt.html";

    fs.readFile("public/"+file, function (err, html) {
        if (err) {
            throw err;
        }
        response.writeHeader(200, {"Content-Type": "text/html", 'Set-Cookie': 'lang=pt'});
        response.write(html);
        response.end();
    });
});

/*
router.get("/en", function(request, response) {

    var file = "index.html";

    fs.readFile("public/"+file, function (err, html) {
        if (err) {
            throw err;
        }
        response.writeHeader(200, {"Content-Type": "text/html"});
        response.write(html);
        response.end();
    });
}); */

var server = http.createServer(router);
server.listen(process.env.PORT || 80, function(){
  console.log('listening on', server.address().port);
});<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react'
import { Howl } from './howler'
import { noop } from './utils'

class ReactHowler extends Component {
  constructor (props) {
    super(props)
    this.initHowler = this.initHowler.bind(this)
  }

  componentDidMount () {
    this.initHowler()
  }

  componentWillReceiveProps (props) {
    if (props.src !== this.props.src) {
      this.initHowler(props)
    } else {
      this.toggleHowler(props)
    }
  }

  componentWillUnmount () {
    this.destroyHowler()
  }
  /**
   * Create howler object with given props
   */
  initHowler (props = this.props) {
    this.destroyHowler()
    if (typeof Howl !== 'undefined') { // Check if window is available
      this.howler = new Howl({
        src: props.src,
        mute: props.mute,
        loop: props.loop,
        volume: props.volume,
        onend: props.onEnd,
        onplay: props.onPlay,
        onpause: props.onPause,
        onvolume: props.onVolume,
        onstop: props.onStop,
        onload: props.onLoad,
        onloaderror: props.onLoadError
      })

      if (props.playing) {
        this.play();
      }
    }
  }

  /**
   * Stop, unload and destroy howler object
   */
  destroyHowler () {
    if (this.howler) {
      this.howler.off() // Remove event listener
      this.howler.stop() // Stop playback
      this.howler.unload() // Remove sound from pool
      this.howler = null // Destroy it
    }
  }

  toggleHowler (props) {
    (props.playing) ? this.play() : this.pause()
    this.mute(props.mute)
    this.loop(props.loop)

    if (props.volume !== this.props.volume) {
      this.volume(props.volume)
    }

    if (props.seek !== this.seek()) {
      this.seek(props.seek)
    }
  }

  set howler (howl) {
    if (howl) {
      this._howler = howl
    }
  }

  get howler () {
    return this._howler
  }

  /**
   * Begins playback of a sound when not playing
   */
  play () {
    const playing = this.howler.playing()

    if (!playing) {
      this.howler.play()
    }
  }

  /**
   * Pauses playback of sound or group
   * If no id given, pauses all playback
   * @param {Number} id = undefined [sound of group to pause]
   */
  pause (id = undefined) {
    if (id) {
      this.howler.pause(id)
    } else {
      this.howler.pause()
    }
  }

  /**
   * Stops playback of sound or group
   * If no id given, stops all playback
   * @param {Number} id = undefined [sound of group to pause]
   */
  stop (id = undefined) {
    if (id) {
      this.howler.stop(id)
    } else {
      this.howler.stop()
    }
  }

  /**
   * Mutes the sound, but doesn't pause the playback.
   * @param {Boolean} [muted] [True to mute and false to unmute]
   * @param {Number} [id] [The sound ID. If none is passed, all sounds in group are muted]
   */
  mute (...args) {
    this.howler.mute(...args)
  }

  /**
   * Get/set volume of this sound or the group. This method optionally takes 0, 1 or 2 arguments.
   * @param {Number} [volume] [Volume from 0.0 to 1.0]
   * @param {Number} [id] [The sound ID. If none is passed, all sounds in group are muted]
   */
  volume (...args) {
    return this.howler.volume(...args)
  }

  /**
   * Get/set whether to loop the sound or group. This method can optionally take 0, 1 or 2 arguments.
   * @param {Boolean} [loop] [To loop or not to loop, that is the question]
   * @param {Number} [id] [The sound ID. If none is passed, all sounds in group will have their loop property updated]
   */
  loop (...args) {
    return this.howler.loop(...args)
  }

  /**
   * Set/get current position of player
   * @param  {Number} pos [seek player to position]
   * @return {Number}     [return current position]
   */
  seek (pos = null) {
    if (!this.howler) {
      return 0
    }

    if (!pos && pos !== 0) {
      return this.howler.seek()
    }

    if (pos || pos === 0) {
      this.howler.seek(pos)
      return pos
    }
  }

  /**
   * Get the duration of the audio source
   * @return {Number} [Audio length in seconds. Will return 0 until after the load event fires]
   */
  duration () {
    return this.howler.duration()
  }

  /**
   * Only render a placeholder
   */
  render () {
    return React.createElement('div', null)
  }
}

ReactHowler.propTypes = {
  src: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.arrayOf(PropTypes.string)
  ]).isRequired,
  playing: PropTypes.bool,
  mute: PropTypes.bool,
  loop: PropTypes.bool,
  volume: PropTypes.number,
  onEnd: PropTypes.func,
  onPause: PropTypes.func,
  onPlay: PropTypes.func,
  onVolume: PropTypes.func,
  onStop: PropTypes.func,
  onLoad: PropTypes.func,
  onLoadError: PropTypes.func
}

ReactHowler.defaultProps = {
  playing: true, // Enable autoplay by default
  mute: false,
  loop: false,
  volume: 1.0,
  onEnd: noop,
  onPause: noop,
  onPlay: noop,
  onVolume: noop,
  onStop: noop,
  onLoad: noop,
  onLoadError: noop
}

export default ReactHowler
<commit_msg>Remove extra semicolon (Doh! :-/)<commit_after>import React, { Component, PropTypes } from 'react'
import { Howl } from './howler'
import { noop } from './utils'

class ReactHowler extends Component {
  constructor (props) {
    super(props)
    this.initHowler = this.initHowler.bind(this)
  }

  componentDidMount () {
    this.initHowler()
  }

  componentWillReceiveProps (props) {
    if (props.src !== this.props.src) {
      this.initHowler(props)
    } else {
      this.toggleHowler(props)
    }
  }

  componentWillUnmount () {
    this.destroyHowler()
  }
  /**
   * Create howler object with given props
   */
  initHowler (props = this.props) {
    this.destroyHowler()
    if (typeof Howl !== 'undefined') { // Check if window is available
      this.howler = new Howl({
        src: props.src,
        mute: props.mute,
        loop: props.loop,
        volume: props.volume,
        onend: props.onEnd,
        onplay: props.onPlay,
        onpause: props.onPause,
        onvolume: props.onVolume,
        onstop: props.onStop,
        onload: props.onLoad,
        onloaderror: props.onLoadError
      })

      if (props.playing) {
        this.play()
      }
    }
  }

  /**
   * Stop, unload and destroy howler object
   */
  destroyHowler () {
    if (this.howler) {
      this.howler.off() // Remove event listener
      this.howler.stop() // Stop playback
      this.howler.unload() // Remove sound from pool
      this.howler = null // Destroy it
    }
  }

  toggleHowler (props) {
    (props.playing) ? this.play() : this.pause()
    this.mute(props.mute)
    this.loop(props.loop)

    if (props.volume !== this.props.volume) {
      this.volume(props.volume)
    }

    if (props.seek !== this.seek()) {
      this.seek(props.seek)
    }
  }

  set howler (howl) {
    if (howl) {
      this._howler = howl
    }
  }

  get howler () {
    return this._howler
  }

  /**
   * Begins playback of a sound when not playing
   */
  play () {
    const playing = this.howler.playing()

    if (!playing) {
      this.howler.play()
    }
  }

  /**
   * Pauses playback of sound or group
   * If no id given, pauses all playback
   * @param {Number} id = undefined [sound of group to pause]
   */
  pause (id = undefined) {
    if (id) {
      this.howler.pause(id)
    } else {
      this.howler.pause()
    }
  }

  /**
   * Stops playback of sound or group
   * If no id given, stops all playback
   * @param {Number} id = undefined [sound of group to pause]
   */
  stop (id = undefined) {
    if (id) {
      this.howler.stop(id)
    } else {
      this.howler.stop()
    }
  }

  /**
   * Mutes the sound, but doesn't pause the playback.
   * @param {Boolean} [muted] [True to mute and false to unmute]
   * @param {Number} [id] [The sound ID. If none is passed, all sounds in group are muted]
   */
  mute (...args) {
    this.howler.mute(...args)
  }

  /**
   * Get/set volume of this sound or the group. This method optionally takes 0, 1 or 2 arguments.
   * @param {Number} [volume] [Volume from 0.0 to 1.0]
   * @param {Number} [id] [The sound ID. If none is passed, all sounds in group are muted]
   */
  volume (...args) {
    return this.howler.volume(...args)
  }

  /**
   * Get/set whether to loop the sound or group. This method can optionally take 0, 1 or 2 arguments.
   * @param {Boolean} [loop] [To loop or not to loop, that is the question]
   * @param {Number} [id] [The sound ID. If none is passed, all sounds in group will have their loop property updated]
   */
  loop (...args) {
    return this.howler.loop(...args)
  }

  /**
   * Set/get current position of player
   * @param  {Number} pos [seek player to position]
   * @return {Number}     [return current position]
   */
  seek (pos = null) {
    if (!this.howler) {
      return 0
    }

    if (!pos && pos !== 0) {
      return this.howler.seek()
    }

    if (pos || pos === 0) {
      this.howler.seek(pos)
      return pos
    }
  }

  /**
   * Get the duration of the audio source
   * @return {Number} [Audio length in seconds. Will return 0 until after the load event fires]
   */
  duration () {
    return this.howler.duration()
  }

  /**
   * Only render a placeholder
   */
  render () {
    return React.createElement('div', null)
  }
}

ReactHowler.propTypes = {
  src: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.arrayOf(PropTypes.string)
  ]).isRequired,
  playing: PropTypes.bool,
  mute: PropTypes.bool,
  loop: PropTypes.bool,
  volume: PropTypes.number,
  onEnd: PropTypes.func,
  onPause: PropTypes.func,
  onPlay: PropTypes.func,
  onVolume: PropTypes.func,
  onStop: PropTypes.func,
  onLoad: PropTypes.func,
  onLoadError: PropTypes.func
}

ReactHowler.defaultProps = {
  playing: true, // Enable autoplay by default
  mute: false,
  loop: false,
  volume: 1.0,
  onEnd: noop,
  onPause: noop,
  onPlay: noop,
  onVolume: noop,
  onStop: noop,
  onLoad: noop,
  onLoadError: noop
}

export default ReactHowler
<|endoftext|>


<commit_before>"use strict";

var assert = require('assert');

var Categories = {
    Chance: 0,
    Ones:   1
};

var score = function(category, a,b,c,d,e) {
    return a + b + c + d + e;
};

describe('yahtzee', function() {
    describe('scoring', function() {
	it('should score chance as the sum of the dice', function() {
	    assert.equal(1+2+3+4+5, score(Categories.Chance, 1,2,3,4,5));
	});

	it('should score \'ones\' as the sum of those matching ones', function() {
	    assert.equal(3, score(Categories.Ones, 1,1,1,2,3));
	});
    });
});
<commit_msg>simple pass<commit_after>"use strict";

var assert = require('assert');

var Categories = {
    Chance: 0,
    Ones:   1
};

var score = function(category, a,b,c,d,e) {

    var sum = function(x,y) { return x + y; };

    if (category === Categories.Chance) {
	return a + b + c + d + e;
    }
    else if (category === Categories.Ones) {
	return [a,b,c,d,e].filter(function(x) { return x === 1 }).reduce(sum);
    }
};

describe('yahtzee', function() {
    describe('scoring', function() {
	it('should score chance as the sum of the dice', function() {
	    assert.equal(1+2+3+4+5, score(Categories.Chance, 1,2,3,4,5));
	});

	it('should score \'ones\' as the sum of those matching ones', function() {
	    assert.equal(3, score(Categories.Ones, 1,1,1,2,3));
	});
    });
});
<|endoftext|>


<commit_before>/*
 *  Copyright (c) 2017 Viktor Honti
 *  Licensed under the MIT License.
 *  https://github.com/jamonserrano/scrollmarks
 */

"use strict";
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    } else {
        root.ScrollMarks = factory();
	}
}(this, function () {
	// store for the scrollmarks
	const scrollMarks = new Map();
	// documentElement cached
	const documentElement = document.documentElement;
	// browser support idle callback
	const hasIdleCallback = window.requestIdleCallback;
	// maximum allowed timeout (configurable)
	let idleTimeout = 100;
	// index of scrollmarks
	let index = 0;
	// started state
	let started = false;
	// frame counter for scroll events
	let scrollTick = 0;
	// throttle for scroll events (configurable)
	let scrollThrottle = 10;
	// frame counter for resize events
	let resizeTick = 0;
	// throttle for resize events (configurable)
	let resizeThrottle = 30;
	// previous scroll position;
	let previousScroll = 0;
	// previous document height
	let previousHeight = documentElement.offsetHeight;
	// listener for document height changes
	let clock;
	// scroll direction
	let direction;
	// queue for triggered marks
	let queue = [];
	// document was scrolled
	let scrolled = false;
	// document was resized
	let resized = false;
	// event listener properties (false by default)
	let listenerProperties = false;
	// set listener properties object if available
	window.addEventListener("test", null, {
		get passive() {
			listenerProperties = {
				passive: true
			}
		}
	});

	/**
	 * Add a new scrollmark
	 * @param {Object} mark
	 * @return {Number} index
	 */
	function add (mark) {
		const { element, callback, offset, direction, once } = mark;
		// validate params
		if (!(element instanceof HTMLElement)) {
			throw new TypeError(`Parameter 'element' must be an HTML Element, got ${element} instead`);
		}
		if (typeof callback !== 'function') {
			throw new TypeError(`Parameter 'callback' must be a function, got ${callback} instead`);
		}
		if (typeof offset === 'undefined') {
			mark.offset = 0;
		} else if (Number.isNaN(offset) && typeof offset !== 'function') {
			throw new TypeError(`Optional parameter 'offset' must be a number or a function, got ${offset} instead`);
		}
		if (direction && direction !== 'up' && direction !== 'down') {
			throw new TypeError(`Optional parameter 'direction' must be either 'up' or 'down', got ${direction} instead`);
		}
		if (typeof once !== 'undefined' && typeof once !== 'boolean') {
			throw new TypeError(`Optional parameter 'once' must be true or false, got ${once} instead`);
		}

		// add triggerpoint
		calculateTriggerPoint(mark);

		// generate key
		const key = index++;
		mark.key = key;

		// add scrollmark to list
		scrollMarks.set(key, mark);

		// start listening
		if (!started) {
			start();
		}
		// TODO trigger if already passed?
		return key;
	}

	/**
	 * Remove a scrollmark
	 * @param {Number} index scrollmark index
	 */
	function remove (key) {
		scrollMarks.delete(key);
		if (!scrollMarks.size) {
			stop();
		}
	}

	/**
	 * Start listening
	 */
	function start () {
		if (started) {
			return;
		}
		started = true;
		// scroll
		window.addEventListener('scroll', onScroll, listenerProperties);
		// resize
		window.addEventListener('resize', onResize, listenerProperties);
		// document height
		clock = window.requestAnimationFrame(checkState);
		// the document can already be scrolled so run a check
		checkMarks();
	}

	/**
	 * Stop listening
	 */
	function stop () {
		if (!started) {
			return;
		}
		started = false;
		// scroll
		window.removeEventListener('scroll', onScroll, listenerProperties);
		// resize
		window.removeEventListener('resize', onResize, listenerProperties);
		// document height
		window.cancelAnimationFrame(clock);
	}

	/**
	 * Scroll event listener
	 * Calls rAF on every nth event (or nth frame in Chrome and FF)
	 */
	function onScroll () {
		window.requestAnimationFrame(() => scrolled = true);
	}

	/**
	 * Checks if scrollmarks should be triggered
	 */
	function checkMarks () {
		// get scroll position and direction
		const currentScroll = window.pageYOffset;
		direction = previousScroll < currentScroll ? 'down' : 'up';

		scrollMarks.forEach((mark) => {
			const markDirection = mark.direction;
			// 1st check: element is visible and direction matches (or not defined)
			if (mark.element.offsetParent !== null && (!markDirection || markDirection === direction)) {
				const triggerPoint = mark.triggerPoint;
				// 2nd check: element actually crossed the mark (below -> above or above -> below)
				// from https://github.com/imakewebthings/waypoints
				if ((previousScroll < triggerPoint) === (triggerPoint <= currentScroll)) {
					// mark should be triggered
					queue.push(mark);
				}
			}
		});
		// trigger affected marks
		triggerQueue();
		// prepare for next run
		previousScroll = currentScroll;
	}

	/**
	 * Trigger scrollmarks
	 */
	function triggerQueue () {
		// put trigger marks in order
		if (direction === 'down') {
			queue.sort((a,b) => a.triggerPoint - b.triggerPoint);
		} else {
			queue.sort((a,b) => b.triggerPoint - a.triggerPoint);
		}
		// call each mark
		queue.forEach((mark) => {
			// TODO bind the callback?
			mark.callback(mark.element, direction)
			// delete onetime marks
			if (mark.once) {
				remove(mark.key);
			}
		});
		// empty queue
		queue = [];
	}

	/**
	 * Set the resized flag
	 * TODO throttle
	 */
	function onResize () {
		window.requestAnimationFrame(() => resized = true);
	}

	/**
	 * Single handler for scroll, document height, and page resize
	 */
	function checkState () {
		// resize check
		if (resizeTick === resizeThrottle) {
			if (resized) {
				// document was resized
				idle(updateTriggerPoints);
				resized = false;
			} else {
				// check the height
				const height = documentElement.offsetHeight;
				if (previousHeight !== height) {
					idle(updateTriggerPoints);
					previousHeight = height;
				}
			}
			resizeTick = 0;
		} else {
			resizeTick++;
		}

		// scroll check
		if (scrollTick === scrollThrottle) {
			if (scrolled) {
				checkMarks();
				scrolled = false;
			}
			scrollTick = 0;
		} else {
			scrollTick++;
		}

		clock = window.requestAnimationFrame(checkState);
	}

	/**
	 * Update all trigger points
	 */
	function updateTriggerPoints () {
		scrollMarks.forEach(calculateTriggerPoint);
	}

	function idle(func) {
		if (hasIdleCallback) {
			window.requestIdleCallback(func, {timeout: idleTimeout});
		} else {
			window.setTimeout(func, idleTimeout);
		}
	}

	/**
	 * Calculate a trigger point
	 * @param {Object} mark
	 */
	function calculateTriggerPoint (mark) {
		mark.triggerPoint = typeof mark.offset === 'function' ?
			mark.offset(mark.element) :
			mark.element.offsetTop - mark.offset;
	}

	function refresh(key) {
		if (typeof key !== 'undefined' && scrollMarks.has(key)) {
			idle(() => calculateTriggerPoint(scrollMarks.get(key)));
		} else {
			idle(updateTriggerPoints);
		}
	}

	/**
	 * Set options
	 * @param {Object} options
	 */
	function config (options) {
		scrollThrottle = options.scrollThrottle;
		resizeThrottle = options.resizeThrottle;
		idleTimeout = options.idleTimeout;
	}

	return {add, remove, start, stop, config, refresh};
}));<commit_msg>Percentage offset<commit_after>/*
 *  Copyright (c) 2017 Viktor Honti
 *  Licensed under the MIT License.
 *  https://github.com/jamonserrano/scrollmarks
 */

"use strict";
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    } else {
        root.ScrollMarks = factory();
	}
}(this, function () {
	// store for the scrollmarks
	const scrollMarks = new Map();
	// documentElement cached
	const documentElement = document.documentElement;
	// browser support idle callback
	const hasIdleCallback = window.requestIdleCallback;
	// maximum allowed timeout (configurable)
	let idleTimeout = 100;
	// index of scrollmarks
	let index = 0;
	// started state
	let started = false;
	// frame counter for scroll events
	let scrollTick = 0;
	// throttle for scroll events (configurable)
	let scrollThrottle = 10;
	// frame counter for resize events
	let resizeTick = 0;
	// throttle for resize events (configurable)
	let resizeThrottle = 30;
	// previous scroll position;
	let previousScroll = 0;
	// previous document height
	let previousHeight = documentElement.offsetHeight;
	// listener for document height changes
	let clock;
	// scroll direction
	let direction;
	// queue for triggered marks
	let queue = [];
	// document was scrolled
	let scrolled = false;
	// document was resized
	let resized = false;
	// event listener properties (false by default)
	let listenerProperties = false;
	// set listener properties object if available
	window.addEventListener("test", null, {
		get passive() {
			listenerProperties = {
				passive: true
			}
		}
	});

	/**
	 * Add a new scrollmark
	 * @param {Object} mark
	 * @return {Number} index
	 */
	function add (mark) {
		const { element, callback, offset, direction, once } = mark;
		// validate params
		if (!(element instanceof HTMLElement)) {
			throw new TypeError(`Parameter 'element' must be an HTML Element, got ${element} instead`);
		}
		if (typeof callback !== 'function') {
			throw new TypeError(`Parameter 'callback' must be a function, got ${callback} instead`);
		}
		if (typeof offset === 'undefined') {
			mark.offset = 0;
		} else if (typeof offset === 'string' && offset.endsWith('%')) {
			mark.offset = (element) => window.pageYOffset + element.getBoundingClientRect().top - window.innerHeight * parseInt(offset) / 100;
		} else if (Number.isNaN(offset) && typeof offset !== 'function') {
			throw new TypeError(`Optional parameter 'offset' must be a number, a percentage, or a function, got ${offset} instead`);
		}
		if (direction && direction !== 'up' && direction !== 'down') {
			throw new TypeError(`Optional parameter 'direction' must be either 'up' or 'down', got ${direction} instead`);
		}
		if (typeof once !== 'undefined' && typeof once !== 'boolean') {
			throw new TypeError(`Optional parameter 'once' must be true or false, got ${once} instead`);
		}

		// add triggerpoint
		calculateTriggerPoint(mark);

		// generate key
		const key = index++;
		mark.key = key;

		// add scrollmark to list
		scrollMarks.set(key, mark);

		// start listening
		if (!started) {
			start();
		}
		// TODO trigger if already passed?
		return key;
	}

	/**
	 * Remove a scrollmark
	 * @param {Number} index scrollmark index
	 */
	function remove (key) {
		scrollMarks.delete(key);
		if (!scrollMarks.size) {
			stop();
		}
	}

	/**
	 * Start listening
	 */
	function start () {
		if (started) {
			return;
		}
		started = true;
		// scroll
		window.addEventListener('scroll', onScroll, listenerProperties);
		// resize
		window.addEventListener('resize', onResize, listenerProperties);
		// document height
		clock = window.requestAnimationFrame(checkState);
		// the document can already be scrolled so run a check
		checkMarks();
	}

	/**
	 * Stop listening
	 */
	function stop () {
		if (!started) {
			return;
		}
		started = false;
		// scroll
		window.removeEventListener('scroll', onScroll, listenerProperties);
		// resize
		window.removeEventListener('resize', onResize, listenerProperties);
		// document height
		window.cancelAnimationFrame(clock);
	}

	/**
	 * Scroll event listener
	 * Calls rAF on every nth event (or nth frame in Chrome and FF)
	 */
	function onScroll () {
		window.requestAnimationFrame(() => scrolled = true);
	}

	/**
	 * Checks if scrollmarks should be triggered
	 */
	function checkMarks () {
		// get scroll position and direction
		const currentScroll = window.pageYOffset;
		direction = previousScroll < currentScroll ? 'down' : 'up';

		scrollMarks.forEach((mark) => {
			const markDirection = mark.direction;
			// 1st check: element is visible and direction matches (or not defined)
			if (mark.element.offsetParent !== null && (!markDirection || markDirection === direction)) {
				const triggerPoint = mark.triggerPoint;
				// 2nd check: element actually crossed the mark (below -> above or above -> below)
				// from https://github.com/imakewebthings/waypoints
				if ((previousScroll < triggerPoint) === (triggerPoint <= currentScroll)) {
					// mark should be triggered
					queue.push(mark);
				}
			}
		});
		// trigger affected marks
		triggerQueue();
		// prepare for next run
		previousScroll = currentScroll;
	}

	/**
	 * Trigger scrollmarks
	 */
	function triggerQueue () {
		// put trigger marks in order
		if (direction === 'down') {
			queue.sort((a,b) => a.triggerPoint - b.triggerPoint);
		} else {
			queue.sort((a,b) => b.triggerPoint - a.triggerPoint);
		}
		// call each mark
		queue.forEach((mark) => {
			// TODO bind the callback?
			mark.callback(mark.element, direction)
			// delete onetime marks
			if (mark.once) {
				remove(mark.key);
			}
		});
		// empty queue
		queue = [];
	}

	/**
	 * Set the resized flag
	 * TODO throttle
	 */
	function onResize () {
		window.requestAnimationFrame(() => resized = true);
	}

	/**
	 * Single handler for scroll, document height, and page resize
	 */
	function checkState () {
		// resize check
		if (resizeTick === resizeThrottle) {
			if (resized) {
				// document was resized
				idle(updateTriggerPoints);
				resized = false;
			} else {
				// check the height
				const height = documentElement.offsetHeight;
				if (previousHeight !== height) {
					idle(updateTriggerPoints);
					previousHeight = height;
				}
			}
			resizeTick = 0;
		} else {
			resizeTick++;
		}

		// scroll check
		if (scrollTick === scrollThrottle) {
			if (scrolled) {
				checkMarks();
				scrolled = false;
			}
			scrollTick = 0;
		} else {
			scrollTick++;
		}

		clock = window.requestAnimationFrame(checkState);
	}

	/**
	 * Update all trigger points
	 */
	function updateTriggerPoints () {
		scrollMarks.forEach(calculateTriggerPoint);
	}

	function idle(func) {
		if (hasIdleCallback) {
			window.requestIdleCallback(func, {timeout: idleTimeout});
		} else {
			window.setTimeout(func, idleTimeout);
		}
	}

	/**
	 * Calculate a trigger point
	 * @param {Object} mark
	 */
	function calculateTriggerPoint (mark) {
		mark.triggerPoint = typeof mark.offset === 'function' ?
			mark.offset(mark.element) :
			window.pageYOffset + mark.element.getBoundingClientRect().top - mark.offset;
	}

	function refresh(key) {
		if (typeof key !== 'undefined' && scrollMarks.has(key)) {
			idle(() => calculateTriggerPoint(scrollMarks.get(key)));
		} else {
			idle(updateTriggerPoints);
		}
	}

	/**
	 * Set options
	 * @param {Object} options
	 */
	function config (options) {
		scrollThrottle = options.scrollThrottle;
		resizeThrottle = options.resizeThrottle;
		idleTimeout = options.idleTimeout;
	}

	return {add, remove, start, stop, config, refresh};
}));<|endoftext|>