<commit_before>var chai  = require('chai')
  , sinon = require("sinon")
  , sinonChai = require("sinon-chai")
  , cobble = require('../index');

chai.use(sinonChai);
chai.should();

describe(' when using descriptors', function(){

  it( 'should only resolve a prop once', function(){
    var result
      , spyA = sinon.spy()
      , spyB = sinon.spy()
      , spyC = sinon.spy()
      , spyD = sinon.spy();

    result = cobble(
        { prop: spyA }
      , { prop: spyB }
      , { prop: cobble.before(spyC) }
      , { prop: cobble.after(spyD) })

    result.prop()

    spyA.should.have.been.calledOnce
    spyB.should.have.been.calledOnce
    spyC.should.have.been.calledBefore(spyA).and.calledOnce
    spyD.should.have.been.calledAfter(spyC).and.calledOnce
  })

  it( 'should resolve proto props once', function(){
    var result
      , spyA = sinon.spy()
      , spyB = sinon.spy()
      , spyC = sinon.spy()
      , spyD = sinon.spy();

    result = cobble.into(
        Object.create({ prop: spyA })
      , { prop: spyB }
      , { prop: cobble.before(spyC) }
      , { prop: cobble.after(spyD) })

    result.prop()

    spyA.should.have.been.calledOnce
    spyB.should.have.been.calledOnce
    spyC.should.have.been.calledBefore(spyA).and.calledOnce
    spyD.should.have.been.calledAfter(spyC).and.calledOnce
  })

  it( 'should work without a specified method', function(){
    var result
      , spyA = sinon.spy()
      , spyB = sinon.spy()
      , spyC = sinon.spy()
      , spyD = sinon.spy();

    result = cobble(
        { prop: spyA }
      , { prop: spyB }
      , { prop: spyC }
      , { prop: cobble.chain() })

      result.prop()

      spyA.should.have.been.calledOnce
      spyB.should.have.been.calledAfter(spyA).and.calledOnce
      spyC.should.have.been.calledAfter(spyB).and.calledOnce
  })

  it( 'should call the new method before the old', function(){
    var result, i
      , spyA = sinon.spy(), spyB = sinon.spy(), spyC = sinon.spy();

    result = cobble(
        { a: spyA }
      , { a: spyB }
      , { a: cobble.before(spyC) })

      result.a()

      spyC.should.have.been.calledBefore(spyA).and.calledOnce
      spyA.should.have.been.calledBefore(spyB).and.calledOnce
      spyB.should.have.been.calledOnce
  })

  it( 'should compose before the old', function(){
    var result, i
      , spyA = sinon.spy(function( last ){ return last + " A" })
      , spyB = sinon.spy(function( last ){ return last + " B" })
      , spyC = sinon.spy(function( last ){ return last + " C" });

    result = cobble(
        { a: spyA }
      , { a: spyB }
      , { a: cobble.composeBefore(spyC) })

      result.a('start').should.equal('start C A B')

      spyC.should.have.been.calledBefore(spyA).and.calledOnce.and.calledWith("start")
      spyA.should.have.been.calledBefore(spyB).and.calledOnce.and.calledWith("start C")
      spyB.should.have.been.calledOnce.and.calledWith("start C A")
  })

  it( 'should call the new method after the old', function(){
    var result, i
      , spyA = sinon.spy(), spyB = sinon.spy(), spyC = sinon.spy();

    result = cobble(
        { a: spyA }
      , { a: spyB }
      , { a: cobble.after(spyC) })

      result.a()
      spyC.should.have.been.calledAfter(spyB).and.calledOnce
      spyB.should.have.been.calledAfter(spyA).and.calledOnce
      spyA.should.have.been.calledOnce
  })

  it( 'should compose after the old', function(){
    var result, i
      , spyA = sinon.spy(function( last ){ return last + " A" })
      , spyB = sinon.spy(function( last ){ return last + " B" })
      , spyC = sinon.spy(function( last ){ return last + " C" });

    result = cobble(
        { a: spyA }
      , { a: spyB }
      , { a: cobble.compose(spyC) })

      result.a('start').should.equal('start A B C')

      spyC.should.have.been.calledAfter(spyB).and.calledOnce.and.calledWith("start A B")
      spyB.should.have.been.calledAfter(spyA).and.calledOnce.and.calledWith("start A")
      spyA.should.have.been.calledOnce.and.calledWith("start")
  })

  it( 'should call the new method around the old', function(){
    var result, spyA = sinon.spy(), spyB = sinon.spy(), spyC;

    result = cobble(
        { a: spyA }
      , { a: spyB }
      , { a: cobble.around(spyC = sinon.spy(function(wrapped){
            spyA.should.have.not.been.called
            spyB.should.have.not.been.called
            wrapped.call(this)
          }))
        })

      result.a()
      spyC.should.have.been.calledBefore(spyA).and.have.been.calledOnce
      spyB.should.have.been.calledAfter(spyA).and.calledOnce
      spyA.should.have.been.calledOnce
  })

  it( 'should reduce values', function(){
    var spanish = { greet: function(){ return "hola" } }
      , german  = { greet: function(){ return "guten morgen" } }

      , result = cobble(spanish, german, {

          greet: cobble.reduce(function(target, next) {
            return function(){
              return target() + " " + next()
            }
          }, englishHello)

        });

    result.greet().should.equal("hello hola guten morgen")

    function englishHello(){ return "hello" }
  })

  it( 'should concat array', function(){
    var mixinA = { a: [1,2,3] }
      , result = cobble(
          mixinA,
          {
            a: cobble.concat([4,5])
          });

    result.a.length.should.equal(5)
    result.a[4].should.equal(5)
  })

  it( 'should take from a different key', function(){
    var obj = { a: true, c: false, d: 'hi' }
      , result;

    result = cobble(
        obj
      , {  b: cobble.from(obj, 'a') })

    result.b.should.equal(true)

    result = cobble(
        obj
      , {  b: cobble.from('c') })

    result.b.should.equal(false)

    result = cobble(
        obj
      , {  d: cobble.from(obj) })

    result.d.should.equal('hi')
  })

})<commit_msg>update test<commit_after>var chai  = require('chai')
  , sinon = require("sinon")
  , sinonChai = require("sinon-chai")
  , cobble = require('../index');

chai.use(sinonChai);
chai.should();

describe(' when using descriptors', function(){

  it( 'should only resolve a prop once', function(){
    var result
      , spyA = sinon.spy()
      , spyB = sinon.spy()
      , spyC = sinon.spy()
      , spyD = sinon.spy();

    result = cobble(
        { prop: spyA }
      , { prop: spyB }
      , { prop: cobble.before(spyC) }
      , { prop: cobble.after(spyD) })

    result.prop()

    spyA.should.have.been.calledOnce
    spyB.should.have.been.calledOnce
    spyC.should.have.been.calledBefore(spyA).and.calledOnce
    spyD.should.have.been.calledAfter(spyC).and.calledOnce
  })

  it( 'should ignore proto props', function(){
    var result
      , spyA = sinon.spy()
      , spyB = sinon.spy()
      , spyC = sinon.spy()
      , spyD = sinon.spy();

    result = cobble.into(
        Object.create({ prop: spyA })
      , { prop: spyB }
      , { prop: cobble.before(spyC) }
      , { prop: cobble.after(spyD) })

    result.prop()

    spyA.should.not.have.been.called
    spyB.should.have.been.calledOnce
    spyC.should.have.been.calledBefore(spyB).and.calledOnce
    spyD.should.have.been.calledAfter(spyC).and.calledOnce
  })

  // it( 'should resolve proto props once', function(){
  //   var result
  //     , spyA = sinon.spy()
  //     , spyB = sinon.spy()
  //     , spyC = sinon.spy()
  //     , spyD = sinon.spy();

  //   result = cobble.into(
  //       Object.create({ prop: spyA })
  //     , { prop: spyB }
  //     , { prop: cobble.before(spyC) }
  //     , { prop: cobble.after(spyD) })

  //   result.prop()

  //   spyA.should.have.been.calledOnce
  //   spyB.should.have.been.calledOnce
  //   spyC.should.have.been.calledBefore(spyA).and.calledOnce
  //   spyD.should.have.been.calledAfter(spyC).and.calledOnce
  // })

  it( 'should work without a specified method', function(){
    var result
      , spyA = sinon.spy()
      , spyB = sinon.spy()
      , spyC = sinon.spy()
      , spyD = sinon.spy();

    result = cobble(
        { prop: spyA }
      , { prop: spyB }
      , { prop: spyC }
      , { prop: cobble.chain() })

      result.prop()

      spyA.should.have.been.calledOnce
      spyB.should.have.been.calledAfter(spyA).and.calledOnce
      spyC.should.have.been.calledAfter(spyB).and.calledOnce
  })

  it( 'should call the new method before the old', function(){
    var result, i
      , spyA = sinon.spy(), spyB = sinon.spy(), spyC = sinon.spy();

    result = cobble(
        { a: spyA }
      , { a: spyB }
      , { a: cobble.before(spyC) })

      result.a()

      spyC.should.have.been.calledBefore(spyA).and.calledOnce
      spyA.should.have.been.calledBefore(spyB).and.calledOnce
      spyB.should.have.been.calledOnce
  })

  it( 'should compose before the old', function(){
    var result, i
      , spyA = sinon.spy(function( last ){ return last + " A" })
      , spyB = sinon.spy(function( last ){ return last + " B" })
      , spyC = sinon.spy(function( last ){ return last + " C" });

    result = cobble(
        { a: spyA }
      , { a: spyB }
      , { a: cobble.composeBefore(spyC) })

      result.a('start').should.equal('start C A B')

      spyC.should.have.been.calledBefore(spyA).and.calledOnce.and.calledWith("start")
      spyA.should.have.been.calledBefore(spyB).and.calledOnce.and.calledWith("start C")
      spyB.should.have.been.calledOnce.and.calledWith("start C A")
  })

  it( 'should call the new method after the old', function(){
    var result, i
      , spyA = sinon.spy(), spyB = sinon.spy(), spyC = sinon.spy();

    result = cobble(
        { a: spyA }
      , { a: spyB }
      , { a: cobble.after(spyC) })

      result.a()
      spyC.should.have.been.calledAfter(spyB).and.calledOnce
      spyB.should.have.been.calledAfter(spyA).and.calledOnce
      spyA.should.have.been.calledOnce
  })

  it( 'should compose after the old', function(){
    var result, i
      , spyA = sinon.spy(function( last ){ return last + " A" })
      , spyB = sinon.spy(function( last ){ return last + " B" })
      , spyC = sinon.spy(function( last ){ return last + " C" });

    result = cobble(
        { a: spyA }
      , { a: spyB }
      , { a: cobble.compose(spyC) })

      result.a('start').should.equal('start A B C')

      spyC.should.have.been.calledAfter(spyB).and.calledOnce.and.calledWith("start A B")
      spyB.should.have.been.calledAfter(spyA).and.calledOnce.and.calledWith("start A")
      spyA.should.have.been.calledOnce.and.calledWith("start")
  })

  it( 'should call the new method around the old', function(){
    var result, spyA = sinon.spy(), spyB = sinon.spy(), spyC;

    result = cobble(
        { a: spyA }
      , { a: spyB }
      , { a: cobble.around(spyC = sinon.spy(function(wrapped){
            spyA.should.have.not.been.called
            spyB.should.have.not.been.called
            wrapped.call(this)
          }))
        })

      result.a()
      spyC.should.have.been.calledBefore(spyA).and.have.been.calledOnce
      spyB.should.have.been.calledAfter(spyA).and.calledOnce
      spyA.should.have.been.calledOnce
  })

  it( 'should reduce values', function(){
    var spanish = { greet: function(){ return "hola" } }
      , german  = { greet: function(){ return "guten morgen" } }

      , result = cobble(spanish, german, {

          greet: cobble.reduce(function(target, next) {
            return function(){
              return target() + " " + next()
            }
          }, englishHello)

        });

    result.greet().should.equal("hello hola guten morgen")

    function englishHello(){ return "hello" }
  })

  it( 'should concat array', function(){
    var mixinA = { a: [1,2,3] }
      , result = cobble(
          mixinA,
          {
            a: cobble.concat([4,5])
          });

    result.a.length.should.equal(5)
    result.a[4].should.equal(5)
  })

  it( 'should take from a different key', function(){
    var obj = { a: true, c: false, d: 'hi' }
      , result;

    result = cobble(
        obj
      , {  b: cobble.from(obj, 'a') })

    result.b.should.equal(true)

    result = cobble(
        obj
      , {  b: cobble.from('c') })

    result.b.should.equal(false)

    result = cobble(
        obj
      , {  d: cobble.from(obj) })

    result.d.should.equal('hi')
  })

})<|endoftext|>


<commit_before>'use strict';
var _ = require('lodash');
var chalk = require('chalk');
var flatten = require('flat');
var gutil = require('gulp-util');
var Promise = require('bluebird');
var request = require('request-promise');
var stringify = require('json-stable-stringify');
var through = require('through2');

/**
 * Interns.
 */
var locoApi = require('./locoApi');

/**
 * TODO: Add write file method
 * Synchronizr
 * This synchronize the local json token with Loco.
 * @param {object} options Synchronize Loco's options.
 *
 * How the synchronization works?
 * This synchronization is mastered by Loco (http://localise.biz), that is to
 * say the right tokens are always from Loco. You can't erease tokens by simply
 * removes it from the json file, you should do it from Loco.
 *
 * This synchronization processes by multiple tasks:
 * 1. Check if locales given in options are comprised in Loco's locales.
 * If not, add this locale to Loco. All Loco's locales are in 'xx_XX' format
 * like 'en_CA', so your locale in 'xx' like 'en' should be converted to Loco's
 * format.
 *
 * 2. Get all tags available from Loco. If the the tags given in options aren't
 * in Loco yet, create it first. Loco will ignore not existing tags otherwise.
 *
 * 3. Get all remote catalogs associated to the locale. Then for each assets in
 * a catalog, test if the assets is already defined in your json file. You have
 * to convert null asset's values to empty string "" because Loco will not
 * import null value.
 *
 * 4. Import to Loco the processed assets.
 *
 * 5. Tag assets (tag+fuzzy).
 *
 * 6. Finaly, get remote catalog of assets from Loco to write the new json
 * localization file with translations.
 */
function Synchronizr (options) {
    this.api = new locoApi(options.apiKey);
}

Synchronizr.prototype.testLocale = function (locale) {
    var self = this,
        locoLocales = [];

    return this.api
        .getLocales()
        .then(function (apiLocales) {
            _.each(apiLocales, function (apiLocale) {
                locoLocales.push(apiLocale.code);
            });

            if (locoLocales.indexOf(locale) < 0) {
                locoLocales.push(locale);
                gutil.log('Locale "' + locale + '" added.');
                return Promise.resolve(self.api
                    .addLocale({code: locale}));
            }

            return Promise.resolve(true);
        })
        .catch(function (response) {
            gutil.log(
                chalk.red('TestLocale error: \n' + JSON.stringify(response))
            );
        });
};

Synchronizr.prototype.createTags = function (tags) {
    var self = this;

    return this.api
        .getTags()
        .then(function (apiTags) {

            /**
             * Create tags with tags given in series.
             */
            return tags.reduce(function(promise, value) {
                return promise.then(function() {
                    gutil.log('Create tag "' + value + '".');
                    return Promise.resolve(self.api
                        .createTag({name : value}));
                });
            }, Promise.resolve());
        })
        .catch(function (response) {
            gutil.log(
                chalk.red('Create tags error: \n' + JSON.stringify(response))
            );
        });
};

Synchronizr.prototype.process = function (locale, tags, content) {
    var merge = false,
        diff = false,
        self = this;

    return this.api
        .exportLocale(locale, tags)
        .then(function (apiAssets) {
            return self.parseTokens(flatten(JSON.parse(apiAssets)), content);
        })
        .then(function (tokens) {
            merge = tokens.merge;
            diff = tokens.diff;
            return self.import(locale, tokens.diff);
        })
        .then(function (keys) {
            /**
             * Tag assets with tags given in series.
             */
            keys.reduce(function(promise, value) {
                return promise.then(function() {
                    gutil.log('Tag asset "' + value + '" with [' + tags + '].');
                    return Promise.resolve(self.api
                        .tagAsset(value, tags));
                });
            }, Promise.resolve());

            /**
             * Flag assets as fuzzy in series.
             */
            keys.reduce(function(promise, value) {
                return promise.then(function() {
                    if (diff[value] === '' || _.isNull(diff[value])) {
                        return Promise.resolve();
                    }
                    gutil.log('Flag asset "' + value + '" as "Fuzzy".');
                    return Promise.resolve(self.api
                            .setStatus(value, 'fuzzy', locale));
                });
            }, Promise.resolve());

            return merge;
        });
};

Synchronizr.prototype.parseTokens = function (apiAssets, fileTokens) {
    var skipToken = false;

    _.each(fileTokens, function (assetValue, assetToken) {
        _.each(apiAssets, function(apiAssetValue, apiAssetKey) {
            if (apiAssetKey === assetToken) {
                gutil.log(chalk.grey(
                    'Skip existing asset translated: ' + assetToken + '.'
                ));
                delete fileTokens[assetToken];
                skipToken = true;
            }
        });

        if (skipToken) {
            skipToken = false;
            return;
        }

        // TODO: make this an option.
        // Will not import null value.
        if (typeof assetValue === null) {
            fileTokens[assetToken] = '';
        }
    });

    return Promise.resolve({
        diff: _.clone(fileTokens),
        merge: _.extend(fileTokens, apiAssets)
    });
};

Synchronizr.prototype.import = function (locale, tokens) {
    return this.api
        .importAsync(locale, tokens)
        .then(function () {
            var keys = [];
            _.each(tokens, function(token, key) {
                keys.push(key);
                gutil.log(chalk.green('Imported asset: ' + token + '.'));
            });
            return keys;
        });
};

module.exports = Synchronizr;
<commit_msg>[SYNCHRONIZR] update import log<commit_after>'use strict';
var _ = require('lodash');
var chalk = require('chalk');
var flatten = require('flat');
var gutil = require('gulp-util');
var Promise = require('bluebird');
var request = require('request-promise');
var stringify = require('json-stable-stringify');
var through = require('through2');

/**
 * Interns.
 */
var locoApi = require('./locoApi');

/**
 * TODO: Add write file method
 * Synchronizr
 * This synchronize the local json token with Loco.
 * @param {object} options Synchronize Loco's options.
 *
 * How the synchronization works?
 * This synchronization is mastered by Loco (http://localise.biz), that is to
 * say the right tokens are always from Loco. You can't erease tokens by simply
 * removes it from the json file, you should do it from Loco.
 *
 * This synchronization processes by multiple tasks:
 * 1. Check if locales given in options are comprised in Loco's locales.
 * If not, add this locale to Loco. All Loco's locales are in 'xx_XX' format
 * like 'en_CA', so your locale in 'xx' like 'en' should be converted to Loco's
 * format.
 *
 * 2. Get all tags available from Loco. If the the tags given in options aren't
 * in Loco yet, create it first. Loco will ignore not existing tags otherwise.
 *
 * 3. Get all remote catalogs associated to the locale. Then for each assets in
 * a catalog, test if the assets is already defined in your json file. You have
 * to convert null asset's values to empty string "" because Loco will not
 * import null value.
 *
 * 4. Import to Loco the processed assets.
 *
 * 5. Tag assets (tag+fuzzy).
 *
 * 6. Finaly, get remote catalog of assets from Loco to write the new json
 * localization file with translations.
 */
function Synchronizr (options) {
    this.api = new locoApi(options.apiKey);
}

Synchronizr.prototype.testLocale = function (locale) {
    var self = this,
        locoLocales = [];

    return this.api
        .getLocales()
        .then(function (apiLocales) {
            _.each(apiLocales, function (apiLocale) {
                locoLocales.push(apiLocale.code);
            });

            if (locoLocales.indexOf(locale) < 0) {
                locoLocales.push(locale);
                gutil.log('Locale "' + locale + '" added.');
                return Promise.resolve(self.api
                    .addLocale({code: locale}));
            }

            return Promise.resolve(true);
        })
        .catch(function (response) {
            gutil.log(
                chalk.red('TestLocale error: \n' + JSON.stringify(response))
            );
        });
};

Synchronizr.prototype.createTags = function (tags) {
    var self = this;

    return this.api
        .getTags()
        .then(function (apiTags) {

            /**
             * Create tags with tags given in series.
             */
            return tags.reduce(function(promise, value) {
                return promise.then(function() {
                    gutil.log('Create tag "' + value + '".');
                    return Promise.resolve(self.api
                        .createTag({name : value}));
                });
            }, Promise.resolve());
        })
        .catch(function (response) {
            gutil.log(
                chalk.red('Create tags error: \n' + JSON.stringify(response))
            );
        });
};

Synchronizr.prototype.process = function (locale, tags, content) {
    var merge = false,
        diff = false,
        self = this;

    return this.api
        .exportLocale(locale, tags)
        .then(function (apiAssets) {
            return self.parseTokens(flatten(JSON.parse(apiAssets)), content);
        })
        .then(function (tokens) {
            merge = tokens.merge;
            diff = tokens.diff;
            return self.import(locale, tokens.diff);
        })
        .then(function (keys) {
            /**
             * Tag assets with tags given in series.
             */
            keys.reduce(function(promise, value) {
                return promise.then(function() {
                    gutil.log('Tag asset "' + value + '" with [' + tags + '].');
                    return Promise.resolve(self.api
                        .tagAsset(value, tags));
                });
            }, Promise.resolve());

            /**
             * Flag assets as fuzzy in series.
             */
            keys.reduce(function(promise, value) {
                return promise.then(function() {
                    if (diff[value] === '' || _.isNull(diff[value])) {
                        return Promise.resolve();
                    }
                    gutil.log('Flag asset "' + value + '" as "Fuzzy".');
                    return Promise.resolve(self.api
                            .setStatus(value, 'fuzzy', locale));
                });
            }, Promise.resolve());

            return merge;
        });
};

Synchronizr.prototype.parseTokens = function (apiAssets, fileTokens) {
    var skipToken = false;

    _.each(fileTokens, function (assetValue, assetToken) {
        _.each(apiAssets, function(apiAssetValue, apiAssetKey) {
            if (apiAssetKey === assetToken) {
                gutil.log(chalk.grey(
                    'Skip existing asset translated: ' + assetToken + '.'
                ));
                delete fileTokens[assetToken];
                skipToken = true;
            }
        });

        if (skipToken) {
            skipToken = false;
            return;
        }

        // TODO: make this an option.
        // Will not import null value.
        if (typeof assetValue === null) {
            fileTokens[assetToken] = '';
        }
    });

    return Promise.resolve({
        diff: _.clone(fileTokens),
        merge: _.extend(fileTokens, apiAssets)
    });
};

Synchronizr.prototype.import = function (locale, tokens) {
    return this.api
        .importAsync(locale, tokens)
        .then(function () {
            var keys = [];
            _.each(tokens, function(token, key) {
                keys.push(key);
                gutil.log(chalk.green('Imported asset: ' + key + '.'));
            });
            return keys;
        });
};

module.exports = Synchronizr;
<|endoftext|>


<commit_before>'use strict';
var _ = require('lodash');
var chalk = require('chalk');
var gutil = require('gulp-util');
var request = require('request-promise');
var stringify = require('json-stable-stringify');
var through = require('through2');

/**
 * Interns.
 */
var locoApi = require('./locoApi');

/**
 * Synchronizr
 * This synchronize the local json token with Loco.
 * @param {object} options Synchronize Loco's options.
 *
 * How the synchronization works?
 * This synchronization is mastered by Loco (http://localise.biz), that is to
 * say the right tokens are always from Loco. You can't erease tokens by simply
 * removes it from the json file, you should do it from Loco.
 *
 * This synchronization processes by multiple tasks:
 * 1. Check if locales given in options are comprised in Loco's locales.
 * If not, add this locale to Loco. All Loco's locales are in 'xx_XX' format
 * like 'en_CA', so your locale in 'xx' like 'en' should be converted to Loco's
 * format.
 *
 * 2. Get all tags available from Loco. If the the tags given in options aren't
 * in Loco yet, create it first. Loco will ignore not existing tags otherwise.
 *
 * 3. Get all remote catalogs associated to the locale. Then for each assets in
 * a catalog, test if the assets is already defined in your json file. You have
 * to convert null asset's values to empty string "" because Loco will not
 * import null value.
 *
 * 4. Import to Loco the processed assets.
 *
 * 5. Tag assets (tag+fuzzy).
 *
 * 6. Finaly, get remote catalog of assets from Loco to write the new json
 * localization file with translations.
 */
function Synchronizr (options) {
    this.api = new locoApi(options.apiKey);
}

/**
 * TODO: use mapping like [en][en_US, en_CA] ...
 */
Synchronizr.prototype.convertLocale = function (locale) {
    return locale + '_CA';
};

Synchronizr.prototype.testLocale = function (locale) {
    var self = this,
        convertedLocale = this.convertLocale(locale),
        locoLocales = [];

    this.api
        .getLocales()
        .then(function (apiLocales) {
            _.each(apiLocales, function (apiLocale) {
                locoLocales.push(apiLocale.code);
            });

            if (locoLocales.indexOf(convertedLocale) === -1) {
                locoLocales.push(convertedLocale);
                self.api
                    .addLocale({code: convertedLocale});
            }
        });
};

Synchronizr.prototype.createTags = function (tags) {
    var self = this;

    this.api
        .getTags()
        .then(function (apiTags) {
            _.each(tags, function (tag) {
                if (apiTags.indexOf(tag) === -1) {
                    self.api
                        .createTag({name: tag});
                }
            });
        });
}

Synchronizr.prototype.sync = function (locale, tags, content) {
    var skipToken = false,
        self = this;

    this.api
        .getAssets(tags)
        .then(function (apiAssets) {
            var fileTokens = _.clone(content);
            _.each(fileTokens, function (assetValue, assetToken) {
                _.each(JSON.parse(apiAssets), function(apiAssetValue, apiAssetKey) {
                    if (apiAssetValue.id === assetToken) {
                        gutil.log(chalk.blue(
                            'Skip existing asset: ' + assetToken + '.'
                        ));
                        delete fileTokens[assetToken];
                        skipToken = true;
                        return;
                    }
                });

                if (skipToken) {
                    skipToken = false;
                    return;
                }

                // TODO: make this an option.
                // Will not import null value.
                if (typeof assetValue === null) {
                    fileTokens[assetToken] = '';
                }

                gutil.log(chalk.yellow(
                    'Import asset: ' + assetToken + '.'
                ));
            });

            if (Object.keys(fileTokens).length < 1) {
                gutil.log(chalk.green(
                    'No token to synchronize.'
                ));
                return;
            }

            gutil.log(chalk.yellow(
                'Will import '+ Object.keys(fileTokens).length +' asset(s).'
            ));

            // Import tokens to api.
            self.api
                .importAsync('fr_CA', fileTokens)
                .then(function () {
                    // Tag new tokens and set them to fuzzy status.
                    _.each(fileTokens, function (token, tokenKey) {
                        _.each(tags, function (tag) {
                            self.api
                                .tagAsset(tokenKey, tag);
                        });

                        if (token !== '') {
                            self.api
                                .setStatus(tokenKey, 'fuzzy', 'fr_CA');
                        }
                    });

                });
        });
};

module.exports = Synchronizr;
<commit_msg>[PROCESS] add TODO<commit_after>'use strict';
var _ = require('lodash');
var chalk = require('chalk');
var gutil = require('gulp-util');
var request = require('request-promise');
var stringify = require('json-stable-stringify');
var through = require('through2');

/**
 * Interns.
 */
var locoApi = require('./locoApi');

/**
 * TODO: Add write file method
 * Synchronizr
 * This synchronize the local json token with Loco.
 * @param {object} options Synchronize Loco's options.
 *
 * How the synchronization works?
 * This synchronization is mastered by Loco (http://localise.biz), that is to
 * say the right tokens are always from Loco. You can't erease tokens by simply
 * removes it from the json file, you should do it from Loco.
 *
 * This synchronization processes by multiple tasks:
 * 1. Check if locales given in options are comprised in Loco's locales.
 * If not, add this locale to Loco. All Loco's locales are in 'xx_XX' format
 * like 'en_CA', so your locale in 'xx' like 'en' should be converted to Loco's
 * format.
 *
 * 2. Get all tags available from Loco. If the the tags given in options aren't
 * in Loco yet, create it first. Loco will ignore not existing tags otherwise.
 *
 * 3. Get all remote catalogs associated to the locale. Then for each assets in
 * a catalog, test if the assets is already defined in your json file. You have
 * to convert null asset's values to empty string "" because Loco will not
 * import null value.
 *
 * 4. Import to Loco the processed assets.
 *
 * 5. Tag assets (tag+fuzzy).
 *
 * 6. Finaly, get remote catalog of assets from Loco to write the new json
 * localization file with translations.
 */
function Synchronizr (options) {
    this.api = new locoApi(options.apiKey);
}

/**
 * TODO: use mapping like [en][en_US, en_CA] ...
 */
Synchronizr.prototype.convertLocale = function (locale) {
    return locale + '_CA';
};

Synchronizr.prototype.testLocale = function (locale) {
    var self = this,
        convertedLocale = this.convertLocale(locale),
        locoLocales = [];

    this.api
        .getLocales()
        .then(function (apiLocales) {
            _.each(apiLocales, function (apiLocale) {
                locoLocales.push(apiLocale.code);
            });

            if (locoLocales.indexOf(convertedLocale) === -1) {
                locoLocales.push(convertedLocale);
                self.api
                    .addLocale({code: convertedLocale});
            }
        });
};

Synchronizr.prototype.createTags = function (tags) {
    var self = this;

    this.api
        .getTags()
        .then(function (apiTags) {
            _.each(tags, function (tag) {
                if (apiTags.indexOf(tag) === -1) {
                    self.api
                        .createTag({name: tag});
                }
            });
        });
}

Synchronizr.prototype.sync = function (locale, tags, content) {
    var skipToken = false,
        self = this;

    this.api
        .getAssets(tags)
        .then(function (apiAssets) {
            var fileTokens = _.clone(content);
            _.each(fileTokens, function (assetValue, assetToken) {
                _.each(JSON.parse(apiAssets), function(apiAssetValue, apiAssetKey) {
                    if (apiAssetValue.id === assetToken) {
                        gutil.log(chalk.blue(
                            'Skip existing asset: ' + assetToken + '.'
                        ));
                        delete fileTokens[assetToken];
                        skipToken = true;
                        return;
                    }
                });

                if (skipToken) {
                    skipToken = false;
                    return;
                }

                // TODO: make this an option.
                // Will not import null value.
                if (typeof assetValue === null) {
                    fileTokens[assetToken] = '';
                }

                gutil.log(chalk.yellow(
                    'Import asset: ' + assetToken + '.'
                ));
            });

            if (Object.keys(fileTokens).length < 1) {
                gutil.log(chalk.green(
                    'No token to synchronize.'
                ));
                return;
            }

            gutil.log(chalk.yellow(
                'Will import '+ Object.keys(fileTokens).length +' asset(s).'
            ));

            // Import tokens to api.
            self.api
                .importAsync('fr_CA', fileTokens)
                .then(function () {
                    // Tag new tokens and set them to fuzzy status.
                    _.each(fileTokens, function (token, tokenKey) {
                        _.each(tags, function (tag) {
                            self.api
                                .tagAsset(tokenKey, tag);
                        });

                        if (token !== '') {
                            self.api
                                .setStatus(tokenKey, 'fuzzy', 'fr_CA');
                        }
                    });

                });
        });
};

module.exports = Synchronizr;
<|endoftext|>


<commit_before>var EventEmitter = require('events');
var util = require('util');

var filterToolbox = require('./util/filter-toolbox');
var Runtime = require('./engine/runtime');
var sb2import = require('./import/sb2import');

/**
 * Handles connections between blocks, stage, and extensions.
 * @constructor
 */
var VirtualMachine = function () {
    var instance = this;
    // Bind event emitter and runtime to VM instance
    EventEmitter.call(instance);
    /**
     * VM runtime, to store blocks, I/O devices, sprites/targets, etc.
     * @type {!Runtime}
     */
    instance.runtime = new Runtime();
    /**
     * The "currently editing"/selected target ID for the VM.
     * Block events from any Blockly workspace are routed to this target.
     * @type {!string}
     */
    instance.editingTarget = null;
    // Runtime emits are passed along as VM emits.
    instance.runtime.on(Runtime.SCRIPT_GLOW_ON, function (glowData) {
        instance.emit(Runtime.SCRIPT_GLOW_ON, glowData);
    });
    instance.runtime.on(Runtime.SCRIPT_GLOW_OFF, function (glowData) {
        instance.emit(Runtime.SCRIPT_GLOW_OFF, glowData);
    });
    instance.runtime.on(Runtime.BLOCK_GLOW_ON, function (glowData) {
        instance.emit(Runtime.BLOCK_GLOW_ON, glowData);
    });
    instance.runtime.on(Runtime.BLOCK_GLOW_OFF, function (glowData) {
        instance.emit(Runtime.BLOCK_GLOW_OFF, glowData);
    });
    instance.runtime.on(Runtime.PROJECT_RUN_START, function () {
        instance.emit(Runtime.PROJECT_RUN_START);
    });
    instance.runtime.on(Runtime.PROJECT_RUN_STOP, function () {
        instance.emit(Runtime.PROJECT_RUN_STOP);
    });
    instance.runtime.on(Runtime.VISUAL_REPORT, function (visualReport) {
        instance.emit(Runtime.VISUAL_REPORT, visualReport);
    });
    instance.runtime.on(Runtime.SPRITE_INFO_REPORT, function (spriteInfo) {
        instance.emit(Runtime.SPRITE_INFO_REPORT, spriteInfo);
    });

    this.blockListener = this.blockListener.bind(this);
    this.flyoutBlockListener = this.flyoutBlockListener.bind(this);
};

/**
 * Inherit from EventEmitter
 */
util.inherits(VirtualMachine, EventEmitter);

/**
 * Start running the VM - do this before anything else.
 */
VirtualMachine.prototype.start = function () {
    this.runtime.start();
};

/**
 * "Green flag" handler - start all threads starting with a green flag.
 */
VirtualMachine.prototype.greenFlag = function () {
    this.runtime.greenFlag();
};

/**
 * Set whether the VM is in "turbo mode."
 * When true, loops don't yield to redraw.
 * @param {boolean} turboModeOn Whether turbo mode should be set.
 */
VirtualMachine.prototype.setTurboMode = function (turboModeOn) {
    this.runtime.turboMode = !!turboModeOn;
};

/**
 * Set whether the VM is in 2.0 "compatibility mode."
 * When true, ticks go at 2.0 speed (30 TPS).
 * @param {boolean} compatibilityModeOn Whether compatibility mode is set.
 */
VirtualMachine.prototype.setCompatibilityMode = function (compatibilityModeOn) {
    this.runtime.setCompatibilityMode(!!compatibilityModeOn);
};

/**
 * Stop all threads and running activities.
 */
VirtualMachine.prototype.stopAll = function () {
    this.runtime.stopAll();
};

/**
 * Clear out current running project data.
 */
VirtualMachine.prototype.clear = function () {
    this.runtime.dispose();
    this.editingTarget = null;
    this.emitTargetsUpdate();
};

/**
 * Get data for playground. Data comes back in an emitted event.
 */
VirtualMachine.prototype.getPlaygroundData = function () {
    var instance = this;
    // Only send back thread data for the current editingTarget.
    var threadData = this.runtime.threads.filter(function (thread) {
        return thread.target === instance.editingTarget;
    });
    // Remove the target key, since it's a circular reference.
    var filteredThreadData = JSON.stringify(threadData, function (key, value) {
        if (key === 'target') return;
        return value;
    }, 2);
    this.emit('playgroundData', {
        blocks: this.editingTarget.blocks,
        threads: filteredThreadData
    });
};

/**
 * Post I/O data to the virtual devices.
 * @param {?string} device Name of virtual I/O device.
 * @param {object} data Any data object to post to the I/O device.
 */
VirtualMachine.prototype.postIOData = function (device, data) {
    if (this.runtime.ioDevices[device]) {
        this.runtime.ioDevices[device].postData(data);
    }
};

/**
 * Load a project from a Scratch 2.0 JSON representation.
 * @param {?string} json JSON string representing the project.
 */
VirtualMachine.prototype.loadProject = function (json) {
    this.clear();
    // @todo: Handle other formats, e.g., Scratch 1.4, Scratch 3.0.
    sb2import(json, this.runtime);
    // Select the first target for editing, e.g., the first sprite.
    this.editingTarget = this.runtime.targets[1];
    // Update the VM user's knowledge of targets and blocks on the workspace.
    this.emitTargetsUpdate();
    this.emitWorkspaceUpdate();
    this.runtime.setEditingTarget(this.editingTarget);
};

/**
 * Add a single sprite from the "Sprite2" (i.e., SB2 sprite) format.
 * @param {?string} json JSON string representing the sprite.
 */
VirtualMachine.prototype.addSprite2 = function (json) {
    // Select new sprite.
    this.editingTarget = sb2import(json, this.runtime, true);
    // Update the VM user's knowledge of targets and blocks on the workspace.
    this.emitTargetsUpdate();
    this.emitWorkspaceUpdate();
    this.runtime.setEditingTarget(this.editingTarget);
};

/**
 * Add a costume to the current editing target.
 * @param {!object} costumeObject Object representing the costume.
 */
VirtualMachine.prototype.addCostume = function (costumeObject) {
    this.editingTarget.sprite.costumes.push(costumeObject);
    // Switch to the costume.
    this.editingTarget.setCostume(
        this.editingTarget.sprite.costumes.length - 1
    );
};

/**
 * Add a backdrop to the stage.
 * @param {!object} backdropObject Object representing the backdrop.
 */
VirtualMachine.prototype.addBackdrop = function (backdropObject) {
    var stage = this.runtime.getTargetForStage();
    stage.sprite.costumes.push(backdropObject);
    // Switch to the backdrop.
    stage.setCostume(stage.sprite.costumes.length - 1);
};

/**
 * Rename a sprite.
 * @param {string} targetId ID of a target whose sprite to rename.
 * @param {string} newName New name of the sprite.
 */
VirtualMachine.prototype.renameSprite = function (targetId, newName) {
    var target = this.runtime.getTargetById(targetId);
    if (target) {
        if (!target.isSprite()) {
            throw new Error('Cannot rename non-sprite targets.');
        }
        var sprite = target.sprite;
        if (!sprite) {
            throw new Error('No sprite associated with this target.');
        }
        sprite.name = newName;
        this.emitTargetsUpdate();
    } else {
        throw new Error('No target with the provided id.');
    }
};

/**
 * Delete a sprite and all its clones.
 * @param {string} targetId ID of a target whose sprite to delete.
 */
VirtualMachine.prototype.deleteSprite = function (targetId) {
    var target = this.runtime.getTargetById(targetId);
    if (target) {
        if (!target.isSprite()) {
            throw new Error('Cannot delete non-sprite targets.');
        }
        var sprite = target.sprite;
        if (!sprite) {
            throw new Error('No sprite associated with this target.');
        }
        var currentEditingTarget = this.editingTarget;
        for (var i = 0; i < sprite.clones.length; i++) {
            var clone = sprite.clones[i];
            this.runtime.stopForTarget(sprite.clones[i]);
            this.runtime.disposeTarget(sprite.clones[i]);
            // Ensure editing target is switched if we are deleting it.
            if (clone === currentEditingTarget) {
                this.setEditingTarget(this.runtime.targets[0].id);
            }
        }
        // Sprite object should be deleted by GC.
        this.emitTargetsUpdate();
    } else {
        throw new Error('No target with the provided id.');
    }
};

/**
 * Set the renderer for the VM/runtime
 * @param {!RenderWebGL} renderer The renderer to attach
 */
VirtualMachine.prototype.attachRenderer = function (renderer) {
    this.runtime.attachRenderer(renderer);
};

/**
 * Set the audio engine for the VM/runtime
 * @param {!AudioEngine} audioEngine The audio engine to attach
 */
VirtualMachine.prototype.attachAudioEngine = function (audioEngine) {
    this.runtime.attachAudioEngine(audioEngine);
};

/**
 * Handle a Blockly event for the current editing target.
 * @param {!Blockly.Event} e Any Blockly event.
 */
VirtualMachine.prototype.blockListener = function (e) {
    if (this.editingTarget) {
        this.editingTarget.blocks.blocklyListen(e, this.runtime);
    }
};

/**
 * Handle a Blockly event for the flyout.
 * @param {!Blockly.Event} e Any Blockly event.
 */
VirtualMachine.prototype.flyoutBlockListener = function (e) {
    this.runtime.flyoutBlocks.blocklyListen(e, this.runtime);
};

/**
 * Set an editing target. An editor UI can use this function to switch
 * between editing different targets, sprites, etc.
 * After switching the editing target, the VM may emit updates
 * to the list of targets and any attached workspace blocks
 * (see `emitTargetsUpdate` and `emitWorkspaceUpdate`).
 * @param {string} targetId Id of target to set as editing.
 */
VirtualMachine.prototype.setEditingTarget = function (targetId) {
    // Has the target id changed? If not, exit.
    if (targetId === this.editingTarget.id) {
        return;
    }
    var target = this.runtime.getTargetById(targetId);
    if (target) {
        this.editingTarget = target;
        // Emit appropriate UI updates.
        this.emitTargetsUpdate();
        this.emitWorkspaceUpdate();
        this.runtime.setEditingTarget(target);
    }
};

/**
 * Emit metadata about available targets.
 * An editor UI could use this to display a list of targets and show
 * the currently editing one.
 */
VirtualMachine.prototype.emitTargetsUpdate = function () {
    this.emit('targetsUpdate', {
        // [[target id, human readable target name], ...].
        targetList: this.runtime.targets.filter(function (target) {
            // Don't report clones.
            return !target.hasOwnProperty('isOriginal') || target.isOriginal;
        }).map(function (target) {
            return target.toJSON();
        }),
        // Currently editing target id.
        editingTarget: this.editingTarget ? this.editingTarget.id : null
    });
};

/**
 * Emit an Blockly/scratch-blocks compatible XML representation
 * of the current editing target's blocks.
 */
VirtualMachine.prototype.emitWorkspaceUpdate = function () {
    this.emit('workspaceUpdate', {
        xml: this.editingTarget.blocks.toXML()
    });
};

/**
 * Get a target id for a drawable id. Useful for interacting with the renderer
 * @param {int} drawableId The drawable id to request the target id for
 * @returns {?string} The target id, if found. Will also be null if the target found is the stage.
 */
VirtualMachine.prototype.getTargetIdForDrawableId = function (drawableId) {
    var target = this.runtime.getTargetByDrawableId(drawableId);
    if (target.hasOwnProperty('id') && target.hasOwnProperty('isStage') && !target.isStage) {
        return target.id;
    }
    return null;
};

/**
 * Put a target into a "drag" state, during which its X/Y positions will be unaffected
 * by blocks.
 * @param {string} targetId The id for the target to put into a drag state
 */
VirtualMachine.prototype.startDrag = function (targetId) {
    var target = this.runtime.getTargetById(targetId);
    if (target) {
        target.startDrag();
        this.setEditingTarget(target.id);
    }
};

/**
 * Remove a target from a drag state, so blocks may begin affecting X/Y position again
 * @param {string} targetId The id for the target to remove from the drag state
 */
VirtualMachine.prototype.stopDrag = function (targetId) {
    var target = this.runtime.getTargetById(targetId);
    if (target) target.stopDrag();
};

/**
 * Post/edit sprite info for the current editing target.
 * @param {object} data An object with sprite info data to set.
 */
VirtualMachine.prototype.postSpriteInfo = function (data) {
    this.editingTarget.postSpriteInfo(data);
};

/**
 * Filter Blockly toolbox XML and return a copy which only contains blocks with
 * existent opcodes. Categories with no valid children will be removed.
 * @param {HTMLElement} toolbox Blockly toolbox XML node
 * @returns {HTMLElement} filtered toolbox XML node
 */
VirtualMachine.prototype.filterToolbox = function (toolbox) {
    var opcodes = Object.keys(this.runtime._primitives)
        .concat(Object.keys(this.runtime._hats));
    return filterToolbox(toolbox, opcodes);
};

module.exports = VirtualMachine;
<commit_msg>Add check to make sure target exists<commit_after>var EventEmitter = require('events');
var util = require('util');

var filterToolbox = require('./util/filter-toolbox');
var Runtime = require('./engine/runtime');
var sb2import = require('./import/sb2import');

/**
 * Handles connections between blocks, stage, and extensions.
 * @constructor
 */
var VirtualMachine = function () {
    var instance = this;
    // Bind event emitter and runtime to VM instance
    EventEmitter.call(instance);
    /**
     * VM runtime, to store blocks, I/O devices, sprites/targets, etc.
     * @type {!Runtime}
     */
    instance.runtime = new Runtime();
    /**
     * The "currently editing"/selected target ID for the VM.
     * Block events from any Blockly workspace are routed to this target.
     * @type {!string}
     */
    instance.editingTarget = null;
    // Runtime emits are passed along as VM emits.
    instance.runtime.on(Runtime.SCRIPT_GLOW_ON, function (glowData) {
        instance.emit(Runtime.SCRIPT_GLOW_ON, glowData);
    });
    instance.runtime.on(Runtime.SCRIPT_GLOW_OFF, function (glowData) {
        instance.emit(Runtime.SCRIPT_GLOW_OFF, glowData);
    });
    instance.runtime.on(Runtime.BLOCK_GLOW_ON, function (glowData) {
        instance.emit(Runtime.BLOCK_GLOW_ON, glowData);
    });
    instance.runtime.on(Runtime.BLOCK_GLOW_OFF, function (glowData) {
        instance.emit(Runtime.BLOCK_GLOW_OFF, glowData);
    });
    instance.runtime.on(Runtime.PROJECT_RUN_START, function () {
        instance.emit(Runtime.PROJECT_RUN_START);
    });
    instance.runtime.on(Runtime.PROJECT_RUN_STOP, function () {
        instance.emit(Runtime.PROJECT_RUN_STOP);
    });
    instance.runtime.on(Runtime.VISUAL_REPORT, function (visualReport) {
        instance.emit(Runtime.VISUAL_REPORT, visualReport);
    });
    instance.runtime.on(Runtime.SPRITE_INFO_REPORT, function (spriteInfo) {
        instance.emit(Runtime.SPRITE_INFO_REPORT, spriteInfo);
    });

    this.blockListener = this.blockListener.bind(this);
    this.flyoutBlockListener = this.flyoutBlockListener.bind(this);
};

/**
 * Inherit from EventEmitter
 */
util.inherits(VirtualMachine, EventEmitter);

/**
 * Start running the VM - do this before anything else.
 */
VirtualMachine.prototype.start = function () {
    this.runtime.start();
};

/**
 * "Green flag" handler - start all threads starting with a green flag.
 */
VirtualMachine.prototype.greenFlag = function () {
    this.runtime.greenFlag();
};

/**
 * Set whether the VM is in "turbo mode."
 * When true, loops don't yield to redraw.
 * @param {boolean} turboModeOn Whether turbo mode should be set.
 */
VirtualMachine.prototype.setTurboMode = function (turboModeOn) {
    this.runtime.turboMode = !!turboModeOn;
};

/**
 * Set whether the VM is in 2.0 "compatibility mode."
 * When true, ticks go at 2.0 speed (30 TPS).
 * @param {boolean} compatibilityModeOn Whether compatibility mode is set.
 */
VirtualMachine.prototype.setCompatibilityMode = function (compatibilityModeOn) {
    this.runtime.setCompatibilityMode(!!compatibilityModeOn);
};

/**
 * Stop all threads and running activities.
 */
VirtualMachine.prototype.stopAll = function () {
    this.runtime.stopAll();
};

/**
 * Clear out current running project data.
 */
VirtualMachine.prototype.clear = function () {
    this.runtime.dispose();
    this.editingTarget = null;
    this.emitTargetsUpdate();
};

/**
 * Get data for playground. Data comes back in an emitted event.
 */
VirtualMachine.prototype.getPlaygroundData = function () {
    var instance = this;
    // Only send back thread data for the current editingTarget.
    var threadData = this.runtime.threads.filter(function (thread) {
        return thread.target === instance.editingTarget;
    });
    // Remove the target key, since it's a circular reference.
    var filteredThreadData = JSON.stringify(threadData, function (key, value) {
        if (key === 'target') return;
        return value;
    }, 2);
    this.emit('playgroundData', {
        blocks: this.editingTarget.blocks,
        threads: filteredThreadData
    });
};

/**
 * Post I/O data to the virtual devices.
 * @param {?string} device Name of virtual I/O device.
 * @param {object} data Any data object to post to the I/O device.
 */
VirtualMachine.prototype.postIOData = function (device, data) {
    if (this.runtime.ioDevices[device]) {
        this.runtime.ioDevices[device].postData(data);
    }
};

/**
 * Load a project from a Scratch 2.0 JSON representation.
 * @param {?string} json JSON string representing the project.
 */
VirtualMachine.prototype.loadProject = function (json) {
    this.clear();
    // @todo: Handle other formats, e.g., Scratch 1.4, Scratch 3.0.
    sb2import(json, this.runtime);
    // Select the first target for editing, e.g., the first sprite.
    this.editingTarget = this.runtime.targets[1];
    // Update the VM user's knowledge of targets and blocks on the workspace.
    this.emitTargetsUpdate();
    this.emitWorkspaceUpdate();
    this.runtime.setEditingTarget(this.editingTarget);
};

/**
 * Add a single sprite from the "Sprite2" (i.e., SB2 sprite) format.
 * @param {?string} json JSON string representing the sprite.
 */
VirtualMachine.prototype.addSprite2 = function (json) {
    // Select new sprite.
    this.editingTarget = sb2import(json, this.runtime, true);
    // Update the VM user's knowledge of targets and blocks on the workspace.
    this.emitTargetsUpdate();
    this.emitWorkspaceUpdate();
    this.runtime.setEditingTarget(this.editingTarget);
};

/**
 * Add a costume to the current editing target.
 * @param {!object} costumeObject Object representing the costume.
 */
VirtualMachine.prototype.addCostume = function (costumeObject) {
    this.editingTarget.sprite.costumes.push(costumeObject);
    // Switch to the costume.
    this.editingTarget.setCostume(
        this.editingTarget.sprite.costumes.length - 1
    );
};

/**
 * Add a backdrop to the stage.
 * @param {!object} backdropObject Object representing the backdrop.
 */
VirtualMachine.prototype.addBackdrop = function (backdropObject) {
    var stage = this.runtime.getTargetForStage();
    stage.sprite.costumes.push(backdropObject);
    // Switch to the backdrop.
    stage.setCostume(stage.sprite.costumes.length - 1);
};

/**
 * Rename a sprite.
 * @param {string} targetId ID of a target whose sprite to rename.
 * @param {string} newName New name of the sprite.
 */
VirtualMachine.prototype.renameSprite = function (targetId, newName) {
    var target = this.runtime.getTargetById(targetId);
    if (target) {
        if (!target.isSprite()) {
            throw new Error('Cannot rename non-sprite targets.');
        }
        var sprite = target.sprite;
        if (!sprite) {
            throw new Error('No sprite associated with this target.');
        }
        sprite.name = newName;
        this.emitTargetsUpdate();
    } else {
        throw new Error('No target with the provided id.');
    }
};

/**
 * Delete a sprite and all its clones.
 * @param {string} targetId ID of a target whose sprite to delete.
 */
VirtualMachine.prototype.deleteSprite = function (targetId) {
    var target = this.runtime.getTargetById(targetId);
    if (target) {
        if (!target.isSprite()) {
            throw new Error('Cannot delete non-sprite targets.');
        }
        var sprite = target.sprite;
        if (!sprite) {
            throw new Error('No sprite associated with this target.');
        }
        var currentEditingTarget = this.editingTarget;
        for (var i = 0; i < sprite.clones.length; i++) {
            var clone = sprite.clones[i];
            this.runtime.stopForTarget(sprite.clones[i]);
            this.runtime.disposeTarget(sprite.clones[i]);
            // Ensure editing target is switched if we are deleting it.
            if (clone === currentEditingTarget) {
                this.setEditingTarget(this.runtime.targets[0].id);
            }
        }
        // Sprite object should be deleted by GC.
        this.emitTargetsUpdate();
    } else {
        throw new Error('No target with the provided id.');
    }
};

/**
 * Set the renderer for the VM/runtime
 * @param {!RenderWebGL} renderer The renderer to attach
 */
VirtualMachine.prototype.attachRenderer = function (renderer) {
    this.runtime.attachRenderer(renderer);
};

/**
 * Set the audio engine for the VM/runtime
 * @param {!AudioEngine} audioEngine The audio engine to attach
 */
VirtualMachine.prototype.attachAudioEngine = function (audioEngine) {
    this.runtime.attachAudioEngine(audioEngine);
};

/**
 * Handle a Blockly event for the current editing target.
 * @param {!Blockly.Event} e Any Blockly event.
 */
VirtualMachine.prototype.blockListener = function (e) {
    if (this.editingTarget) {
        this.editingTarget.blocks.blocklyListen(e, this.runtime);
    }
};

/**
 * Handle a Blockly event for the flyout.
 * @param {!Blockly.Event} e Any Blockly event.
 */
VirtualMachine.prototype.flyoutBlockListener = function (e) {
    this.runtime.flyoutBlocks.blocklyListen(e, this.runtime);
};

/**
 * Set an editing target. An editor UI can use this function to switch
 * between editing different targets, sprites, etc.
 * After switching the editing target, the VM may emit updates
 * to the list of targets and any attached workspace blocks
 * (see `emitTargetsUpdate` and `emitWorkspaceUpdate`).
 * @param {string} targetId Id of target to set as editing.
 */
VirtualMachine.prototype.setEditingTarget = function (targetId) {
    // Has the target id changed? If not, exit.
    if (targetId === this.editingTarget.id) {
        return;
    }
    var target = this.runtime.getTargetById(targetId);
    if (target) {
        this.editingTarget = target;
        // Emit appropriate UI updates.
        this.emitTargetsUpdate();
        this.emitWorkspaceUpdate();
        this.runtime.setEditingTarget(target);
    }
};

/**
 * Emit metadata about available targets.
 * An editor UI could use this to display a list of targets and show
 * the currently editing one.
 */
VirtualMachine.prototype.emitTargetsUpdate = function () {
    this.emit('targetsUpdate', {
        // [[target id, human readable target name], ...].
        targetList: this.runtime.targets.filter(function (target) {
            // Don't report clones.
            return !target.hasOwnProperty('isOriginal') || target.isOriginal;
        }).map(function (target) {
            return target.toJSON();
        }),
        // Currently editing target id.
        editingTarget: this.editingTarget ? this.editingTarget.id : null
    });
};

/**
 * Emit an Blockly/scratch-blocks compatible XML representation
 * of the current editing target's blocks.
 */
VirtualMachine.prototype.emitWorkspaceUpdate = function () {
    this.emit('workspaceUpdate', {
        xml: this.editingTarget.blocks.toXML()
    });
};

/**
 * Get a target id for a drawable id. Useful for interacting with the renderer
 * @param {int} drawableId The drawable id to request the target id for
 * @returns {?string} The target id, if found. Will also be null if the target found is the stage.
 */
VirtualMachine.prototype.getTargetIdForDrawableId = function (drawableId) {
    var target = this.runtime.getTargetByDrawableId(drawableId);
    if (target && target.hasOwnProperty('id') && target.hasOwnProperty('isStage') && !target.isStage) {
        return target.id;
    }
    return null;
};

/**
 * Put a target into a "drag" state, during which its X/Y positions will be unaffected
 * by blocks.
 * @param {string} targetId The id for the target to put into a drag state
 */
VirtualMachine.prototype.startDrag = function (targetId) {
    var target = this.runtime.getTargetById(targetId);
    if (target) {
        target.startDrag();
        this.setEditingTarget(target.id);
    }
};

/**
 * Remove a target from a drag state, so blocks may begin affecting X/Y position again
 * @param {string} targetId The id for the target to remove from the drag state
 */
VirtualMachine.prototype.stopDrag = function (targetId) {
    var target = this.runtime.getTargetById(targetId);
    if (target) target.stopDrag();
};

/**
 * Post/edit sprite info for the current editing target.
 * @param {object} data An object with sprite info data to set.
 */
VirtualMachine.prototype.postSpriteInfo = function (data) {
    this.editingTarget.postSpriteInfo(data);
};

/**
 * Filter Blockly toolbox XML and return a copy which only contains blocks with
 * existent opcodes. Categories with no valid children will be removed.
 * @param {HTMLElement} toolbox Blockly toolbox XML node
 * @returns {HTMLElement} filtered toolbox XML node
 */
VirtualMachine.prototype.filterToolbox = function (toolbox) {
    var opcodes = Object.keys(this.runtime._primitives)
        .concat(Object.keys(this.runtime._hats));
    return filterToolbox(toolbox, opcodes);
};

module.exports = VirtualMachine;
<|endoftext|>


<commit_before>var options;
chrome.extension.sendRequest({}, function(response) {
	setAllTheSettings(response);
});

function setAllTheSettings(response) {
	options = response;
	bodyClasses = document.body.classList;
	document.body.addEventListener("DOMNodeInserted", eventDispatcher);
	if(options.circled_avatars == "true") {
		bodyClasses.add("btd-circle-avatars");
	}
	if(options.fading_pictures == "true") {
		bodyClasses.add("btd-profile-pictures-fading");
	}
	if(options.reduced_padding == "true") {
		bodyClasses.add("btd-reduced-padding");
	}
	if(options.no_columns_icons == "true") {
		bodyClasses.add("btd-no-columns-icons");
	}
}

function eventDispatcher() {
	// If the event.target is the text (TweetDeck updates timestamp at regular intervals) then we can get the .txt-mute element and tweak it in real-time
	if(event.relatedNode.className.indexOf("txt-mute") != -1 && options.timestamp != "relative") {
		timeIsNotRelative(event.relatedNode, options.timestamp);
	}
	// If it's not a .txt-mute element, it must be a tweet or something similar, let's check it !
	else if(event.target.className && event.target.className.indexOf("stream-item") != -1) {

		// Applying the timestamp
		if(options.timestamp != "relative") {
			if(event.target.querySelector("time")){
				timeIsNotRelative(event.target.querySelector("time > *"), options.timestamp);
			}
		}

		// Tweaking the name displaying
		nameDisplay(event.target.querySelectorAll("a[rel='user']:not(.item-img)"), options.name_display);

		// If asked, removing t.co links
		if(options.url_redirection == "true"){
			useFullUrl(event.target);
		}

		// If asked, creating the non-pic.twitter image previews
		if(options.img_preview != "false"){
			links = event.target.querySelectorAll("p > a[data-full-url]");
			if(links.length > 0) {
			linkUrl = links[0].getAttribute("data-full-url");
				if(linkUrl.indexOf("imgur.com") != -1 && linkUrl.indexOf("/a/") == -1){
					createPreviewDiv(links[0],"imgur");
				} else if(linkUrl.indexOf("d.pr/i") != -1) {
					createPreviewDiv(links[0],"droplr");
				}
			}
		}
	} else if(event.relatedNode.classList.contains("typeahead")) {
		console.log("mention helper !");
		if(options.name_display == "username") {
			for (var i = event.relatedNode.querySelectorAll("strong.fullname").length - 1; i >= 0; i--) {
				event.relatedNode.querySelectorAll("strong.fullname")[i].remove();
			};
			for (var i = event.relatedNode.querySelectorAll("span.username").length - 1; i >= 0; i--) {
				event.relatedNode.querySelectorAll("span.username")[i].style.fontWeight= "bold";
				event.relatedNode.querySelectorAll("span.username")[i].classList.add("fullname");
				event.relatedNode.querySelectorAll("span.username")[i].classList.remove("username");
			};
		}
	}
}

function createPreviewDiv(element, provider) {
	// Getting the full URL for later
	linkURL = element.getAttribute("data-full-url");
	thumbSize = options.img_preview;

	// Creating the elements, replicating the same layout as TweetDeck's one
	previewDiv = document.createElement("div");
	previewDiv.className = "js-media media-preview position-rel btd-preview "+provider;
	previewDivChild = document.createElement("div");
	previewDivChild.className = "js-media-preview-container position-rel margin-vm";
	previewLink = document.createElement("a");
	previewLink.className = "js-media-image-link block med-link media-item media-size-"+thumbSize+"";
	// Little difference, using rel=url otherwhise TweetDeck will treat it as a "real" media preview, therefore "blocking" the click on it
	previewLink.setAttribute("rel","url");
	previewLink.href = linkURL;
	previewLink.setAttribute("target","_blank");

	if(provider == "imgur") {
		// Imgur supported extensions (from http://imgur.com/faq#types)
		extensions = ["jpg","jpeg","gif","png","apng"];

		if(thumbSize == "small") suffix = "t";
		if(thumbSize == "medium") suffix = "m";
		if(thumbSize == "large") suffix = "l";

		var regex = new RegExp(extensions.join('|'));
		var thumbnailUrl;

		// If link has an extension, replacing it by .jpg (png would be possible, but we don't want to have big previews)
		if (regex.test(previewLink.href)) {
			for (var i = extensions.length - 1; i >= 0; i--) {
				thumbnailUrl = linkURL.replace("."+extensions[i],suffix+".jpg");
			};
		} else {
			// Even if it doesn't have any extension, putting it one at the end so we can get something
			thumbnailUrl = linkURL+suffix+".jpg";
		}

		// Replacing imgur.com by i.imgur.com and http by https
		thumbnailUrl = thumbnailUrl.replace("/imgur.com","/i.imgur.com").replace("http","https");

		// Removing the "gallery" in URLs to allow a nice preview
		if(thumbnailUrl.indexOf("gallery") != -1) {
			thumbnailUrl = thumbnailUrl.replace("/gallery","");
		}

	} else if(provider == "droplr") {

		// Depending of the thumbSize option we're getting d.pr/i/1234/small or d.pr/i/1234/medium (it seems like Droplr hasn't a "large" option)
		if(thumbSize == "small") {
			suffix = thumbSize;
		} else {
			suffix = "medium";
		}
		// Removing the last "/" if present and adding one+suffix
		thumbnailUrl = linkURL.replace(/\/$/,"");
		thumbnailUrl = thumbnailUrl+"/"+suffix;
	}

	// Applying our thumbnail as a background-image of the preview div
	previewLink.style.backgroundImage = "url("+thumbnailUrl+")";

	// Constructing our final div
	previewDivChild.appendChild(previewLink);
	previewDiv.appendChild(previewDivChild);

	// Adding it next to the <p> element, just before <footer> in a tweet
	pElement = element.parentNode.parentNode.querySelector("p.js-tweet-text");
	if(pElement.nextElementSibling) {
		pElement.parentNode.insertBefore(previewDiv, pElement.nextElementSibling);
	}
}

function timeIsNotRelative(element, mode) {
	// Getting the timestamp of an item
	d = element.parentNode.getAttribute("data-time");
	// Creating a Date object with it
	td = new Date(parseInt(d));

	// Creating year/day/month/minutes/hours variables and applying the lead zeros if necessary
	year = td.getFullYear();
	if(year < 10) year = "0"+year;
	month = td.getMonth()+1;
	if(month < 10) month = "0"+month;
	minutes = td.getMinutes();
	if(minutes < 10) minutes = "0"+minutes;
	hours = td.getHours();
	if(hours < 10) hours = "0"+hours;
	day = td.getDate();
	if(day < 10) day = "0"+day;

	// Handling "US" date format
	if(mode == "absolute_us"){
		dateString =  month+"/"+day+"/"+year+" "+hours+":"+minutes;
	} else {
		dateString =  day+"/"+month+"/"+year+" "+hours+":"+minutes;
	}
	// Changing the content of the "time > a" element with the absolute time
	element.innerHTML = dateString;
	element.classList.add("txt-mute");
}

function nameDisplay(elements, mode) {
	if(mode == "username"){
		// If we just want the @username.
		for (var i = elements.length - 1; i >= 0; i--) {
			// If the username is NOT in a tweet (typically in a <p> element), do the thing
			if(elements[i].parentNode.tagName != "P") {
				// Removing "http://twitter.com" and the last "/" in the link to get the @username
				username = elements[i].getAttribute("href").replace(/http(|s):\/\/twitter.com\//,"").replace(/\//g,"");

				// Placing the username in b.fullname if found or in span.username
				if(elements[i].querySelector("b.fullname")){
					elements[i].querySelector("b.fullname").innerHTML = username;
				} else {
					elements[i].innerHTML = username;
				}
				if(elements[i].querySelector("span.username")){
					elements[i].querySelector("span.username").remove();
				}
			}
		};
	} else if(mode == "fullname") {
		// If we just want the fullname, basically we exterminate the usernames
		for (var i = document.querySelectorAll(".username").length - 1; i >= 0; i--) {
		// Ignoring the elements argument because I'm lazy and it works so, hey ?
		document.querySelectorAll(".username")[i].remove();
		};
	}
}

function useFullUrl(element) {
	// Pretty easy, getting the data-full-url content and applying it as href in the links. Bye bye t.co !
	links = element.querySelectorAll("a[data-full-url]");
	for (var i = links.length - 1; i >= 0; i--) {
		fullLink = links[i].getAttribute("data-full-url");
		links[i].href = fullLink;
	};
}
<commit_msg>Removed the preview handling for imgur /gallery/ links, because sometimes a gallery and an image can have the same id, and that's not good.<commit_after>var options;
chrome.extension.sendRequest({}, function(response) {
	setAllTheSettings(response);
});

function setAllTheSettings(response) {
	options = response;
	bodyClasses = document.body.classList;
	document.body.addEventListener("DOMNodeInserted", eventDispatcher);
	if(options.circled_avatars == "true") {
		bodyClasses.add("btd-circle-avatars");
	}
	if(options.fading_pictures == "true") {
		bodyClasses.add("btd-profile-pictures-fading");
	}
	if(options.reduced_padding == "true") {
		bodyClasses.add("btd-reduced-padding");
	}
	if(options.no_columns_icons == "true") {
		bodyClasses.add("btd-no-columns-icons");
	}
}

function eventDispatcher() {
	// If the event.target is the text (TweetDeck updates timestamp at regular intervals) then we can get the .txt-mute element and tweak it in real-time
	if(event.relatedNode.className.indexOf("txt-mute") != -1 && options.timestamp != "relative") {
		timeIsNotRelative(event.relatedNode, options.timestamp);
	}
	// If it's not a .txt-mute element, it must be a tweet or something similar, let's check it !
	else if(event.target.className && event.target.className.indexOf("stream-item") != -1) {

		// Applying the timestamp
		if(options.timestamp != "relative") {
			if(event.target.querySelector("time")){
				timeIsNotRelative(event.target.querySelector("time > *"), options.timestamp);
			}
		}

		// Tweaking the name displaying
		nameDisplay(event.target.querySelectorAll("a[rel='user']:not(.item-img)"), options.name_display);

		// If asked, removing t.co links
		if(options.url_redirection == "true"){
			useFullUrl(event.target);
		}

		// If asked, creating the non-pic.twitter image previews
		if(options.img_preview != "false"){
			links = event.target.querySelectorAll("p > a[data-full-url]");
			if(links.length > 0) {
			linkUrl = links[0].getAttribute("data-full-url");
				if(linkUrl.indexOf("imgur.com") != -1 && linkUrl.indexOf("/a/") == -1 && linkUrl.indexOf("/gallery/") == -1){
					createPreviewDiv(links[0],"imgur");
				} else if(linkUrl.indexOf("d.pr/i") != -1) {
					createPreviewDiv(links[0],"droplr");
				}
			}
		}
	} else if(event.relatedNode.classList.contains("typeahead")) {
		console.log("mention helper !");
		if(options.name_display == "username") {
			for (var i = event.relatedNode.querySelectorAll("strong.fullname").length - 1; i >= 0; i--) {
				event.relatedNode.querySelectorAll("strong.fullname")[i].remove();
			};
			for (var i = event.relatedNode.querySelectorAll("span.username").length - 1; i >= 0; i--) {
				event.relatedNode.querySelectorAll("span.username")[i].style.fontWeight= "bold";
				event.relatedNode.querySelectorAll("span.username")[i].classList.add("fullname");
				event.relatedNode.querySelectorAll("span.username")[i].classList.remove("username");
			};
		}
	}
}

function createPreviewDiv(element, provider) {
	// Getting the full URL for later
	linkURL = element.getAttribute("data-full-url");
	thumbSize = options.img_preview;

	// Creating the elements, replicating the same layout as TweetDeck's one
	previewDiv = document.createElement("div");
	previewDiv.className = "js-media media-preview position-rel btd-preview "+provider;
	previewDivChild = document.createElement("div");
	previewDivChild.className = "js-media-preview-container position-rel margin-vm";
	previewLink = document.createElement("a");
	previewLink.className = "js-media-image-link block med-link media-item media-size-"+thumbSize+"";
	// Little difference, using rel=url otherwhise TweetDeck will treat it as a "real" media preview, therefore "blocking" the click on it
	previewLink.setAttribute("rel","url");
	previewLink.href = linkURL;
	previewLink.setAttribute("target","_blank");

	if(provider == "imgur") {
		// Imgur supported extensions (from http://imgur.com/faq#types)
		extensions = ["jpg","jpeg","gif","png","apng"];

		if(thumbSize == "small") suffix = "t";
		if(thumbSize == "medium") suffix = "m";
		if(thumbSize == "large") suffix = "l";

		var regex = new RegExp(extensions.join('|'));
		var thumbnailUrl;

		// If link has an extension, replacing it by .jpg (png would be possible, but we don't want to have big previews)
		if (regex.test(previewLink.href)) {
			for (var i = extensions.length - 1; i >= 0; i--) {
				thumbnailUrl = linkURL.replace("."+extensions[i],suffix+".jpg");
			};
		} else {
			// Even if it doesn't have any extension, putting it one at the end so we can get something
			thumbnailUrl = linkURL+suffix+".jpg";
		}

		// Replacing imgur.com by i.imgur.com and http by https
		thumbnailUrl = thumbnailUrl.replace("/imgur.com","/i.imgur.com").replace("http","https");

	} else if(provider == "droplr") {

		// Depending of the thumbSize option we're getting d.pr/i/1234/small or d.pr/i/1234/medium (it seems like Droplr hasn't a "large" option)
		if(thumbSize == "small") {
			suffix = thumbSize;
		} else {
			suffix = "medium";
		}
		// Removing the last "/" if present and adding one+suffix
		thumbnailUrl = linkURL.replace(/\/$/,"");
		thumbnailUrl = thumbnailUrl+"/"+suffix;
	}

	// Applying our thumbnail as a background-image of the preview div
	previewLink.style.backgroundImage = "url("+thumbnailUrl+")";

	// Constructing our final div
	previewDivChild.appendChild(previewLink);
	previewDiv.appendChild(previewDivChild);

	// Adding it next to the <p> element, just before <footer> in a tweet
	pElement = element.parentNode.parentNode.querySelector("p.js-tweet-text");
	if(pElement.nextElementSibling) {
		pElement.parentNode.insertBefore(previewDiv, pElement.nextElementSibling);
	}
}

function timeIsNotRelative(element, mode) {
	// Getting the timestamp of an item
	d = element.parentNode.getAttribute("data-time");
	// Creating a Date object with it
	td = new Date(parseInt(d));

	// Creating year/day/month/minutes/hours variables and applying the lead zeros if necessary
	year = td.getFullYear();
	if(year < 10) year = "0"+year;
	month = td.getMonth()+1;
	if(month < 10) month = "0"+month;
	minutes = td.getMinutes();
	if(minutes < 10) minutes = "0"+minutes;
	hours = td.getHours();
	if(hours < 10) hours = "0"+hours;
	day = td.getDate();
	if(day < 10) day = "0"+day;

	// Handling "US" date format
	if(mode == "absolute_us"){
		dateString =  month+"/"+day+"/"+year+" "+hours+":"+minutes;
	} else {
		dateString =  day+"/"+month+"/"+year+" "+hours+":"+minutes;
	}
	// Changing the content of the "time > a" element with the absolute time
	element.innerHTML = dateString;
	element.classList.add("txt-mute");
}

function nameDisplay(elements, mode) {
	if(mode == "username"){
		// If we just want the @username.
		for (var i = elements.length - 1; i >= 0; i--) {
			// If the username is NOT in a tweet (typically in a <p> element), do the thing
			if(elements[i].parentNode.tagName != "P") {
				// Removing "http://twitter.com" and the last "/" in the link to get the @username
				username = elements[i].getAttribute("href").replace(/http(|s):\/\/twitter.com\//,"").replace(/\//g,"");

				// Placing the username in b.fullname if found or in span.username
				if(elements[i].querySelector("b.fullname")){
					elements[i].querySelector("b.fullname").innerHTML = username;
				} else {
					elements[i].innerHTML = username;
				}
				if(elements[i].querySelector("span.username")){
					elements[i].querySelector("span.username").remove();
				}
			}
		};
	} else if(mode == "fullname") {
		// If we just want the fullname, basically we exterminate the usernames
		for (var i = document.querySelectorAll(".username").length - 1; i >= 0; i--) {
		// Ignoring the elements argument because I'm lazy and it works so, hey ?
		document.querySelectorAll(".username")[i].remove();
		};
	}
}

function useFullUrl(element) {
	// Pretty easy, getting the data-full-url content and applying it as href in the links. Bye bye t.co !
	links = element.querySelectorAll("a[data-full-url]");
	for (var i = links.length - 1; i >= 0; i--) {
		fullLink = links[i].getAttribute("data-full-url");
		links[i].href = fullLink;
	};
}
<|endoftext|>


<commit_before>(function(w, d){
	var identificator = 'fucking-eu-cookies';
	var userVariable = 'fucking_eu_config';
	var noShowEvent = 'no-show';
	var showEvent = 'show';
	var hideEvent = 'hide';
	var consentReason = 'consent';
	var cookiesOK = 'CookiesOK';
	var DOMContentLoaded = 'DOMContentLoaded';
	var load = 'load';
	var click = 'click';

	var includes = <%= JSON.stringify({
				version: pkg.version,
				css: css,
				l18n: l18n,
				options: options
			}, null, '\t') %>;

	var config = {};

	function init() {
		w[userVariable] = w[userVariable] || {};
		config = buildConfig(includes, w[userVariable]);

		invokeEvent('init', includes.version);

		if(d.cookie.indexOf(identificator) !== -1) {
			invokeEvent(noShowEvent, consentReason);
			return;
		}

		if(navigator[cookiesOK]) {
			addCookie( 'auto-'+cookiesOK );
			invokeEvent(noShowEvent, 'plugin '+cookiesOK);
			return;
		}

		if( !w.addEventListener ) {
			//To keep things simple are old browsers unsupported
			invokeEvent(noShowEvent, 'unsupported browser');
			return;
		}

		if ( d.readyState === 'complete' ) {
			setTimeout( dry );
		} else {
			d.addEventListener( DOMContentLoaded, completed, false );
			w.addEventListener( load, completed, false );
		}
	};

	function completed() {
		d.removeEventListener( DOMContentLoaded, completed, false );
		w.removeEventListener( load, completed, false );
		dry();
	}

	function dry(){
		invokeEvent(showEvent);

		var html = '%t %m '+
		'<button>%a</button>';
		html = html
			.replace('%t', config.l18n.text)
			.replace('%l', config.l18n.link)
			.replace('%m', config.l18n.more)
			.replace('%a', config.l18n.accept);
		var body = d.body;
		var head = d.head;
		var style = document.createElement('style');
		style.type = 'text/css';
		style.appendChild(d.createTextNode(includes.css));

		var div = d.createElement('div');
		div.className = identificator + ' fucking-priority';
		div.innerHTML = html;
		head.appendChild(style);
		var insertTo = config.options.insertTo;
		var targetElement;
        if (insertTo == 'body-begin') {
            body.insertBefore(div, body.firstChild);
        } else if (insertTo == 'body-end') {
            body.insertBefore(div, null);
        } else if (targetElement = document.getElementById(insertTo)) {
            targetElement.insertBefore(div, null);
        }
		div.getElementsByTagName('button')[0].addEventListener(click, function(){ consent( div ); });
		var a = div.getElementsByTagName('a')[0];
		a.addEventListener(click, function(){ invokeEvent('open-more'); });
		if(config.options.popupMore) {
			a.setAttribute('target', '_blank');
		}
	}

	function buildConfig(defaults, mods) {
		var config = {};
		for(key in defaults) {
			if( typeof mods[key] === 'undefined' ) {
				config[key] = defaults[key];
			}
			else if(typeof mods[key] === 'object'){
				config[key] = buildConfig(defaults[key], mods[key]);
			}
			else {
				config[key] = mods[key];
			}
		}
		return config;
	}

	function invokeEvent( action, label ) {
		if (typeof(config.options.callback) === 'function') {
			config.options.callback( action, label );
		}
		var dataLayer = config.options.dataLayerName;
		if (dataLayer && w[dataLayer] && typeof(w[dataLayer].push) === 'function') {
			w[dataLayer].push({
				'event': identificator,
				'action': action,
				'label': label
			});
		}
	}

	function consent( div ) {
		d.body.removeChild( div );
		invokeEvent(hideEvent, consentReason);
		addCookie();
	}

	function addCookie( reason ) {
		if (typeof reason === 'undefined') {
			reason = '1';
		}
		var date = new Date();
		date.setFullYear(date.getFullYear() + 1);
		var expires = '; expires=' + date.toGMTString();
		d.cookie = identificator + '=' + encodeURIComponent(reason) + expires + '; path=/';
	}

	init();

})(window, window.document);
<commit_msg>cookies.js: Converted spaces to tabs<commit_after>(function(w, d){
	var identificator = 'fucking-eu-cookies';
	var userVariable = 'fucking_eu_config';
	var noShowEvent = 'no-show';
	var showEvent = 'show';
	var hideEvent = 'hide';
	var consentReason = 'consent';
	var cookiesOK = 'CookiesOK';
	var DOMContentLoaded = 'DOMContentLoaded';
	var load = 'load';
	var click = 'click';

	var includes = <%= JSON.stringify({
				version: pkg.version,
				css: css,
				l18n: l18n,
				options: options
			}, null, '\t') %>;

	var config = {};

	function init() {
		w[userVariable] = w[userVariable] || {};
		config = buildConfig(includes, w[userVariable]);

		invokeEvent('init', includes.version);

		if(d.cookie.indexOf(identificator) !== -1) {
			invokeEvent(noShowEvent, consentReason);
			return;
		}

		if(navigator[cookiesOK]) {
			addCookie( 'auto-'+cookiesOK );
			invokeEvent(noShowEvent, 'plugin '+cookiesOK);
			return;
		}

		if( !w.addEventListener ) {
			//To keep things simple are old browsers unsupported
			invokeEvent(noShowEvent, 'unsupported browser');
			return;
		}

		if ( d.readyState === 'complete' ) {
			setTimeout( dry );
		} else {
			d.addEventListener( DOMContentLoaded, completed, false );
			w.addEventListener( load, completed, false );
		}
	};

	function completed() {
		d.removeEventListener( DOMContentLoaded, completed, false );
		w.removeEventListener( load, completed, false );
		dry();
	}

	function dry(){
		invokeEvent(showEvent);

		var html = '%t %m '+
		'<button>%a</button>';
		html = html
			.replace('%t', config.l18n.text)
			.replace('%l', config.l18n.link)
			.replace('%m', config.l18n.more)
			.replace('%a', config.l18n.accept);
		var body = d.body;
		var head = d.head;
		var style = document.createElement('style');
		style.type = 'text/css';
		style.appendChild(d.createTextNode(includes.css));

		var div = d.createElement('div');
		div.className = identificator + ' fucking-priority';
		div.innerHTML = html;
		head.appendChild(style);
		var insertTo = config.options.insertTo;
		var targetElement;
		if (insertTo == 'body-begin') {
			body.insertBefore(div, body.firstChild);
		} else if (insertTo == 'body-end') {
			body.insertBefore(div, null);
		} else if (targetElement = document.getElementById(insertTo)) {
			targetElement.insertBefore(div, null);
		}
		div.getElementsByTagName('button')[0].addEventListener(click, function(){ consent( div ); });
		var a = div.getElementsByTagName('a')[0];
		a.addEventListener(click, function(){ invokeEvent('open-more'); });
		if(config.options.popupMore) {
			a.setAttribute('target', '_blank');
		}
	}

	function buildConfig(defaults, mods) {
		var config = {};
		for(key in defaults) {
			if( typeof mods[key] === 'undefined' ) {
				config[key] = defaults[key];
			}
			else if(typeof mods[key] === 'object'){
				config[key] = buildConfig(defaults[key], mods[key]);
			}
			else {
				config[key] = mods[key];
			}
		}
		return config;
	}

	function invokeEvent( action, label ) {
		if (typeof(config.options.callback) === 'function') {
			config.options.callback( action, label );
		}
		var dataLayer = config.options.dataLayerName;
		if (dataLayer && w[dataLayer] && typeof(w[dataLayer].push) === 'function') {
			w[dataLayer].push({
				'event': identificator,
				'action': action,
				'label': label
			});
		}
	}

	function consent( div ) {
		d.body.removeChild( div );
		invokeEvent(hideEvent, consentReason);
		addCookie();
	}

	function addCookie( reason ) {
		if (typeof reason === 'undefined') {
			reason = '1';
		}
		var date = new Date();
		date.setFullYear(date.getFullYear() + 1);
		var expires = '; expires=' + date.toGMTString();
		d.cookie = identificator + '=' + encodeURIComponent(reason) + expires + '; path=/';
	}

	init();

})(window, window.document);
<|endoftext|>


<commit_before>/**
 * Creates an instance of FEEDEATER.  Valid options:
 *   'feed' specifies a tumblr feed.  Defaults to tumblr_api_read.
 *   'target' specifies default target element.  Defaults to 'tumblr-posts'.
 *   'renderers' allows addition/override of rendering routines.  Must be an
 *     object of the form {'postType': rendering_fn, ...}.
 *
 * @constructor
 * @this {FEEDEATER}
 * @param {object} opts Desired options.
 */

FEEDEATER = function FEEDEATER(opts) {
  opts = opts || {};
  this.feed = opts['feed'] || tumblr_api_read;
  this.target = opts['target'] || 'tumblr-posts';

  this.load_renderers();
  if (opts['renderers']) {
    for (var r in opts['renderers']) {
      this.renderers.r = opts['renderers'][r];
    }
  }
};

/**
 * Renders a Tumblr feed into a target element, according to desired options.
 * Class method.
 * See FEEDEATER constructor for valid options.
 *
 * @param {object} opts Desired options.
 */
FEEDEATER.render_posts = function (opts) {
  var f = new FEEDEATER(opts);
  f.render();
};

/**
 * Renders a Tumblr feed into a target element, according to desired options.
 * Instance method.
 * See FEEDEATER constructor for valid options.
 *
 * @param {object} opts Desired options.
 */
FEEDEATER.prototype.render = function render(opts) {
  opts = opts || {};
  var target = opts['target'] || this.target;
  var html = this.render_to_string(opts);
  this.render_html_into_target(html, target);
};

/**
 * Renders a Tumblr feed to an HTML string, according to desired options.
 * See FEEDEATER constructor for valid options.
 *
 * @param {object} opts Desired options.
 * @return {string} HTML string.
 */
FEEDEATER.prototype.render_to_string = function render_to_string(opts) {
  opts = opts || {};
  var posts = opts['posts'] || this.feed.posts;
  var html = '';
  for (var i in posts) {
    html += this.render_post_to_html(posts[i]);
  }
  return html;
};

//// private methods follow.

/**
 * @private
 *
 * Renders given html into given target.
 * Target may be specified as an HTML element, as a jQuery selector,
 * as an element ID name, or as a jQuery selector string.
 *
 * @param {string} html The HTML to render.
 * @param target The target to render into.
 */
FEEDEATER.prototype.render_html_into_target = function render_html_into_target(html, target) {
  if (jQuery && target.html) { // jQuery selector object
    target.html(html);
  }
  else if (target.innerHTML) { // element
    target.innerHTML = html;
  }
  else if (target) {
    // target is string; try to use it as an element id, otherwise if
    // jQuery's loaded, use it as a jQuery selector string
    var elt = document.getElementById(target);
    if (elt) {
      elt.innerHTML = html;
    }
    else if (jQuery) {
      $(target).html(html);
    }
  }
};

/**
 * @private
 *
 * Returns an HTML rendering of a post.
 *
 * @param {post} post A Tumblr post object.
 */
FEEDEATER.prototype.render_post_to_html = function render_post_to_html(post){
  return '
' +
         this.renderers[post.type](post) +
         '
';
};

/**
 * @private
 *
 * Loads the specified renderers into this.
 *
 * @param {object} renderers Renderers to load.  Defaults to FEEDEATER.renderers.
 */
FEEDEATER.prototype.load_renderers = function load_renderers(renderers) {
  renderers = renderers || FEEDEATER.renderers;
  this.renderers = this.renderers || {};
  for (var r in renderers) {
    this.renderers[r] = renderers[r];
  }
};

/**
 * @private
 *
 * Rendering functions for each post.type, keyed by post.type.
 * All functions take 'post' as single argument.
 * Renderers are loaded into a FEEDEATER object at instantiation time
 * by use of load_renderers().
 */
FEEDEATER.renderers = {
  regular: function render_regular(post) {
    return '
' + post['regular-title'] + '
' +
           '
' + post['date'] + '
' +
           '
' + post['regular-body'] + '
';
  },
  link: function render_link(post) {
    return '' + post['link-text'] + '' +
           '
' + post['date'] + '
';
  },
  quote: function render_quote(post) {
    return '
' + post['quote-text'] + '
' +
           '
' + post['quote-source'] + '
' +
           '
' + post['date'] + '
';
  },
  photo: function render_photo(post) {
    return '' +
           '
' + post['photo-caption'] + '
' +
           '
' + post['date'] + '
';
  },
  conversation: function render_conversation(post) {
    var convo = '',
        lines = post['conversation'];

    convo = '
' + post['conversation-title'] + '
' +
            '
' + post['date'] + '
';
    for (var i in lines) {
      convo += '
' + lines[i].name +
               '' + lines[i].phrase + '
';
    }
    return convo;
  },
  video: function render_video(post) {
    return '
' + post['video-caption'] + '
' +
           '
' + post['date'] + '
' + post['video-player'];
  },
  audio: function render_audio(post) {
    return '
' + post['audio-caption'] + '
' +
           '
' + post['date'] + '
' + post['audio-player'];
  },
  answer: function render_answer(post) {
    return '
'+ post['question'] + '
' +
           '
' + post['answer'] + '
' +
           '
' + post['date'] + '
';
  }
};

<commit_msg>fixed bug around image_width<commit_after>/**
 * Creates an instance of FEEDEATER.  Valid options:
 *   'feed' specifies a tumblr feed.  Defaults to tumblr_api_read.
 *   'target' specifies default target element.  Defaults to 'tumblr-posts'.
 *   'image_width' specifies desired image width.  Defaults to 500.
 *   'renderers' allows addition/override of rendering routines.  Must be an
 *     object of the form {'postType': rendering_fn, ...}.
 *
 * @constructor
 * @this {FEEDEATER}
 * @param {object} opts Desired options.
 */

FEEDEATER = function FEEDEATER(opts) {
  opts = opts || {};

  this.feed = opts['feed'] || tumblr_api_read;
  this.target = opts['target'] || 'tumblr-posts';
  this.image_width = opts['image_width'] || 500;

  this.load_renderers();
  if (opts['renderers']) {
    for (var r in opts['renderers']) {
      this.renderers.r = opts['renderers'][r];
    }
  }
};

/**
 * Renders a Tumblr feed into a target element, according to desired options.
 * Class method.
 * See FEEDEATER constructor for valid options.
 *
 * @param {object} opts Desired options.
 */
FEEDEATER.render_posts = function (opts) {
  var f = new FEEDEATER(opts);
  f.render();
};

/**
 * Renders a Tumblr feed into a target element, according to desired options.
 * Instance method.
 * See FEEDEATER constructor for valid options.
 *
 * @param {object} opts Desired options.
 */
FEEDEATER.prototype.render = function render(opts) {
  opts = opts || {};
  var target = opts['target'] || this.target;
  var html = this.render_to_string(opts);
  this.render_html_into_target(html, target);
};

/**
 * Renders a Tumblr feed to an HTML string, according to desired options.
 * See FEEDEATER constructor for valid options.
 *
 * @param {object} opts Desired options.
 * @return {string} HTML string.
 */
FEEDEATER.prototype.render_to_string = function render_to_string(opts) {
  opts = opts || {};
  var posts = opts['posts'] || this.feed.posts;
  var html = '';
  for (var i in posts) {
    html += this.render_post_to_html(posts[i]);
  }
  return html;
};

//// private methods follow.

/**
 * @private
 *
 * Renders given html into given target.
 * Target may be specified as an HTML element, as a jQuery selector,
 * as an element ID name, or as a jQuery selector string.
 *
 * @param {string} html The HTML to render.
 * @param target The target to render into.
 */
FEEDEATER.prototype.render_html_into_target = function render_html_into_target(html, target) {
  if (jQuery && target.html) { // jQuery selector object
    target.html(html);
  }
  else if (target.innerHTML) { // element
    target.innerHTML = html;
  }
  else if (target) {
    // target is string; try to use it as an element id, otherwise if
    // jQuery's loaded, use it as a jQuery selector string
    var elt = document.getElementById(target);
    if (elt) {
      elt.innerHTML = html;
    }
    else if (jQuery) {
      $(target).html(html);
    }
  }
};

/**
 * @private
 *
 * Returns an HTML rendering of a post.
 *
 * @param {post} post A Tumblr post object.
 */
FEEDEATER.prototype.render_post_to_html = function render_post_to_html(post){
  return '
' +
         this.renderers[post.type](post, this) +
         '
';
};

/**
 * @private
 *
 * Loads the specified renderers into this.
 *
 * @param {object} renderers Renderers to load.  Defaults to FEEDEATER.renderers.
 */
FEEDEATER.prototype.load_renderers = function load_renderers(renderers) {
  renderers = renderers || FEEDEATER.renderers;
  this.renderers = this.renderers || {};
  for (var r in renderers) {
    this.renderers[r] = renderers[r];
  }
};

/**
 * @private
 *
 * Rendering functions for each post.type, keyed by post.type.
 * All functions receive 'post' as first argument, 'this' (the FEEDEATER
 * instance) as second argument.
 * Renderers are loaded into a FEEDEATER object at instantiation time
 * by use of load_renderers().
 */
FEEDEATER.renderers = {
  regular: function render_regular(post) {
    return '
' + post['regular-title'] + '
' +
           '
' + post['date'] + '
' +
           '
' + post['regular-body'] + '
';
  },
  link: function render_link(post) {
    return '' + post['link-text'] + '' +
           '
' + post['date'] + '
';
  },
  quote: function render_quote(post) {
    return '
' + post['quote-text'] + '
' +
           '
' + post['quote-source'] + '
' +
           '
' + post['date'] + '
';
  },
  photo: function render_photo(post, that) {
    return '' +
           '
' + post['photo-caption'] + '
' +
           '
' + post['date'] + '
';
  },
  conversation: function render_conversation(post) {
    var convo = '',
        lines = post['conversation'];

    convo = '
' + post['conversation-title'] + '
' +
            '
' + post['date'] + '
';
    for (var i in lines) {
      convo += '
' + lines[i].name +
               '' + lines[i].phrase + '
';
    }
    return convo;
  },
  video: function render_video(post) {
    return '
' + post['video-caption'] + '
' +
           '
' + post['date'] + '
' + post['video-player'];
  },
  audio: function render_audio(post) {
    return '
' + post['audio-caption'] + '
' +
           '
' + post['date'] + '
' + post['audio-player'];
  },
  answer: function render_answer(post) {
    return '
'+ post['question'] + '
' +
           '
' + post['answer'] + '
' +
           '
' + post['date'] + '
';
  }
};

<|endoftext|>