<commit_before>/**
 * Module for async transloadit jobs.
 * It attempts to mimic the transloadit jquery plugin where it makes sense.
 *
 * @notes Currently relies on a global instance of $.jsonp
 *
 * @params {string} key - Creates a factory for Transformit instances
 *
 * @package transformit
 * @author Derek Reynolds <drk@diy.org>
 */

/**
 * Dependencies
 */
var events             = require('events');
var asyncf             = require('async-form');
var util               = require('util');

/**
 * Lib
 */
var fetchBoredInstance = require('./lib/bored-instance');
var createAssemblyId   = require('./lib/create-assembly-id');
var pulser             = require('./lib/pulser');

/**
 * Constants
 */
var PROTOCOL = (document.location.protocol === 'https:') ? 'https://' : 'http://';

/**
 * Constructor
 *
 * @param {object} $form - Can be an instance of FormData or a form element
 * @param {object} options
 *   service: the transloadit service to submit/poll
 *   fields: any additional fields to send along w/ the form submission
 *   params: transloadit request params
 *     auth: { public_key }
 *     encoder_key:
 *   templateId: A string or synchronous function that returns a template id
 *   trigger: String or boolean. False will not autosubmit. ('submit', 'change')
 *   interval: how often in poll assembly url in ms
 *   timeout: how long before submit/poll request times out
 */
function Transformit ($form, options) {
    this.$form   = $form;

    // set options
    this.service     = options.service || PROTOCOL + 'api2.transloadit.com';
    this.fields      = options.fields  || true;
    this.params      = options.params  || {};
    this.signature   = options.signature || null;
    this.templateId  = options.templateId || '';
    this.trigger     = options.trigger || 'submit'; // false, 'submit' || 'change'

    // proxy to transpulser when required
    this.interval = parseInt(options.interval, 10) || 2500;
    this.timeout  = parseInt(options.timeout, 10)  || 8000;
    this.notFoundAttempts = 15;
    this.connectionAttempts = 15;

    this.seq = 0;

    this.bindDomListeners();
    this.appendFields(this.fields);
}

/**
 * Inherits
 */
util.inherits(Transformit, events.EventEmitter);

/**
 * Will bind 'submit' or 'change' handlers on the provided
 * form depending on options.trigger. This is only relevant if $form
 * is an actual form element.
 */
Transformit.prototype.bindDomListeners = function () {
    var me = this;

    // Bow out
    if (!this.trigger ||
        (typeof FormData !== 'undefined' && this.$form instanceof FormData))
        return;

    // Submit
    if (this.trigger === 'submit') {
        this.$form.onsubmit = function submitHandler (e) {
            e.preventDefault();
            e.stopPropagation();
            me.upload();
            return false;
        };
    }

    // Change
    function changeHandler () {
        me.upload();
    }

    if (this.trigger === 'change') {
        var $inputs = this.$form.querySelectorAll('input[type=file]');
        if ($inputs.length <= 0) return;

        for (var i = 0; i <= $inputs.length; i++) {
            $inputs[0].addEventListener(changeHandler);
        }
    }
};

/**
 * Asynchronously uploads the files contained in the form.
 *
 * @param {function} callback
 */
Transformit.prototype.upload = function (callback) {
    var me = this;

    function handleBoredInstance (err, instance) {
        me.sendForm(PROTOCOL + instance, callback);
    }

    fetchBoredInstance(this.service, handleBoredInstance);
};

/**
 * Asynchronously sends $form to transloadit. This is dependent on
 * having a bored instance url.
 *
 * @notes: https://github.com/transloadit/jquery-sdk/blob/master/js/lib/jquery.transloadit2.js#L126-L164
 *
 * @param {string} instance - Bored instance url
 * @param {function} callback
 */
Transformit.prototype.sendForm = function (instance, callback) {
    var me = this;
    var id = createAssemblyId();
    var assemblyUrl = instance + '/assemblies/' + id;

    asyncf(this.$form, {
        'action': assemblyUrl + '?redirect=false',
        'body': {
            'params': JSON.stringify(this.getParams())
        },
        'success': function asyncSuccess (err, body) {
            if (err) {
                callback(err);
                return me.emit('error', err);
            }
            callback(null, body);
        },
        'error': function asyncError (err) {
            me.emit('error', err);
            callback(err);
        }
    });

    me.startPulse(assemblyUrl);
};

/**
 * Creates a new pulse instance for the given assembly url and
 * proxies all the relevent events.
 *
 * @param {string} assymblyUrl
 */
Transformit.prototype.startPulse = function (assemblyUrl) {
    var me = this;

    this.pulse = pulser(assemblyUrl, { service: this.service });

    this.pulse.on('success', function (assembly) {
        me.emit('success', assembly);
    });
    this.pulse.on('error', function (err) {
        me.emit('error', err);
    });
    this.pulse.on('progress', function (received, expected, assembly) {
        me.emit('progress', received, expected, assembly);
    });

    // Asynchronously kick off the pulse process w/ noop callback
    this.pulse.start(function () {  });
};

/**
 * Getter for params.
 *
 * @param {File} file - File being uploaded (for calculating template id)
 */
Transformit.prototype.getParams = function (file) {
    this.params.auth = { key: this.key };
    this.params.template_id = this.getTemplateId(file);

    return this.params;
};

/**
 * Returns template id based on options.templateId.
 *
 * If `options.templateId` is a function it will be provided the File object as
 * the first argument w/ the Transformit instance as the context.
 *
 * @param {File} file - File being uploaded.
 */
Transformit.prototype.getTemplateId = function (file) {
    var id = '';

    if (typeof this.templateId === 'function') id = this.templateId.apply(this, [file]);

    if (typeof this.templateId === 'string') id = this.templateId;

    if (typeof id !== 'string') this.emit('error', 'templateId must yield a string. Got: ', typeof id, id);

    return id;
};

/**
 * Dynamically append a field to the form after Transformit has been
 * instantiated.
 *
 * @param {string} fieldName
 * @param {string} val
 */
Transformit.prototype.appendField = function (fieldName, val) {
    if (this.$form.tagName === 'FORM') {
        var $field = document.createElement('input');
        $field.name  = fieldName;
        $field.value = val;
        this.$form.appendChild($field);
    }

    if (typeof FormData !== 'undefined'
        && this.$form instanceof FormData)
        this.$form.append(fieldName, val);
};

/**
 * Convenience wrapper for appending multiple fields.
 */
Transformit.prototype.appendFields = function (fields) {
    for (var name in fields) {
        this.appendField(name, fields[name]);
    }
};

/**
 * Export
 */
var transformit = function (key) {
    Transformit.prototype.key = key || '';

    return function createTransformitInstance ($form, options) {
        return new Transformit($form, options);
    };
};

/**
 * Export pulser to allow use outside of Transformit instance.
 */
transformit.pulser = pulser;

module.exports = transformit;
<commit_msg>Fix appending fields for ie9<commit_after>/**
 * Module for async transloadit jobs.
 * It attempts to mimic the transloadit jquery plugin where it makes sense.
 *
 * @notes Currently relies on a global instance of $.jsonp
 *
 * @params {string} key - Creates a factory for Transformit instances
 *
 * @package transformit
 * @author Derek Reynolds <drk@diy.org>
 */

/**
 * Dependencies
 */
var events             = require('events');
var asyncf             = require('async-form');
var util               = require('util');

/**
 * Lib
 */
var fetchBoredInstance = require('./lib/bored-instance');
var createAssemblyId   = require('./lib/create-assembly-id');
var pulser             = require('./lib/pulser');

/**
 * Constants
 */
var PROTOCOL = (document.location.protocol === 'https:') ? 'https://' : 'http://';

/**
 * Constructor
 *
 * @param {object} $form - Can be an instance of FormData or a form element
 * @param {object} options
 *   service: the transloadit service to submit/poll
 *   fields: any additional fields to send along w/ the form submission
 *   params: transloadit request params
 *     auth: { public_key }
 *     encoder_key:
 *   templateId: A string or synchronous function that returns a template id
 *   trigger: String or boolean. False will not autosubmit. ('submit', 'change')
 *   interval: how often in poll assembly url in ms
 *   timeout: how long before submit/poll request times out
 */
function Transformit ($form, options) {
    this.$form   = $form;

    // set options
    this.service     = options.service || PROTOCOL + 'api2.transloadit.com';
    this.fields      = options.fields  || true;
    this.params      = options.params  || {};
    this.signature   = options.signature || null;
    this.templateId  = options.templateId || '';
    this.trigger     = options.trigger || 'submit'; // false, 'submit' || 'change'

    // proxy to transpulser when required
    this.interval = parseInt(options.interval, 10) || 2500;
    this.timeout  = parseInt(options.timeout, 10)  || 8000;
    this.notFoundAttempts = 15;
    this.connectionAttempts = 15;

    this.seq = 0;

    this.bindDomListeners();
    this.appendFields(this.fields);
}

/**
 * Inherits
 */
util.inherits(Transformit, events.EventEmitter);

/**
 * Will bind 'submit' or 'change' handlers on the provided
 * form depending on options.trigger. This is only relevant if $form
 * is an actual form element.
 */
Transformit.prototype.bindDomListeners = function () {
    var me = this;

    // Bow out
    if (!this.trigger ||
        (typeof FormData !== 'undefined' && this.$form instanceof FormData))
        return;

    // Submit
    if (this.trigger === 'submit') {
        this.$form.onsubmit = function submitHandler (e) {
            e.preventDefault();
            e.stopPropagation();
            me.upload();
            return false;
        };
    }

    // Change
    function changeHandler () {
        me.upload();
    }

    if (this.trigger === 'change') {
        var $inputs = this.$form.querySelectorAll('input[type=file]');
        if ($inputs.length <= 0) return;

        for (var i = 0; i <= $inputs.length; i++) {
            $inputs[0].addEventListener(changeHandler);
        }
    }
};

/**
 * Asynchronously uploads the files contained in the form.
 *
 * @param {function} callback
 */
Transformit.prototype.upload = function (callback) {
    var me = this;

    function handleBoredInstance (err, instance) {
        me.sendForm(PROTOCOL + instance, callback);
    }

    fetchBoredInstance(this.service, handleBoredInstance);
};

/**
 * Asynchronously sends $form to transloadit. This is dependent on
 * having a bored instance url.
 *
 * @notes: https://github.com/transloadit/jquery-sdk/blob/master/js/lib/jquery.transloadit2.js#L126-L164
 *
 * @param {string} instance - Bored instance url
 * @param {function} callback
 */
Transformit.prototype.sendForm = function (instance, callback) {
    var me = this;
    var id = createAssemblyId();
    var assemblyUrl = instance + '/assemblies/' + id;

    asyncf(this.$form, {
        'action': assemblyUrl + '?redirect=false',
        'body': {
            'params': JSON.stringify(this.getParams())
        },
        'success': function asyncSuccess (err, body) {
            if (err) {
                callback(err);
                return me.emit('error', err);
            }
            callback(null, body);
        },
        'error': function asyncError (err) {
            me.emit('error', err);
            callback(err);
        }
    });

    me.startPulse(assemblyUrl);
};

/**
 * Creates a new pulse instance for the given assembly url and
 * proxies all the relevent events.
 *
 * @param {string} assymblyUrl
 */
Transformit.prototype.startPulse = function (assemblyUrl) {
    var me = this;

    this.pulse = pulser(assemblyUrl, { service: this.service });

    this.pulse.on('success', function (assembly) {
        me.emit('success', assembly);
    });
    this.pulse.on('error', function (err) {
        me.emit('error', err);
    });
    this.pulse.on('progress', function (received, expected, assembly) {
        me.emit('progress', received, expected, assembly);
    });

    // Asynchronously kick off the pulse process w/ noop callback
    this.pulse.start(function () {  });
};

/**
 * Getter for params.
 *
 * @param {File} file - File being uploaded (for calculating template id)
 */
Transformit.prototype.getParams = function (file) {
    this.params.auth = { key: this.key };
    this.params.template_id = this.getTemplateId(file);

    return this.params;
};

/**
 * Returns template id based on options.templateId.
 *
 * If `options.templateId` is a function it will be provided the File object as
 * the first argument w/ the Transformit instance as the context.
 *
 * @param {File} file - File being uploaded.
 */
Transformit.prototype.getTemplateId = function (file) {
    var id = '';

    if (typeof this.templateId === 'function') id = this.templateId.apply(this, [file]);

    if (typeof this.templateId === 'string') id = this.templateId;

    if (typeof id !== 'string') this.emit('error', 'templateId must yield a string. Got: ', typeof id, id);

    return id;
};

/**
 * Dynamically append a field to the form after Transformit has been
 * instantiated.
 *
 * @param {string} fieldName
 * @param {string} val
 */
Transformit.prototype.appendField = function (fieldName, val) {
    if (this.$form.tagName === 'FORM') {
        var $field = document.createElement('input');
        $field.name  = fieldName;
        $field.value = val;
        $field.type  = 'hidden'; // must provide type for ie9
        this.$form.appendChild($field);
    }

    if (typeof FormData !== 'undefined'
        && this.$form instanceof FormData)
        this.$form.append(fieldName, val);
};

/**
 * Convenience wrapper for appending multiple fields.
 */
Transformit.prototype.appendFields = function (fields) {
    for (var name in fields) {
        this.appendField(name, fields[name]);
    }
};

/**
 * Export
 */
var transformit = function (key) {
    Transformit.prototype.key = key || '';

    return function createTransformitInstance ($form, options) {
        return new Transformit($form, options);
    };
};

/**
 * Export pulser to allow use outside of Transformit instance.
 */
transformit.pulser = pulser;

module.exports = transformit;
<|endoftext|>


<commit_before>define(['index', 'src/eltable'], function(Focss, eltable) {
  // IMPORTANT: We can't do pseudo-elements!
  // e.g. ::first-letter, ::before, ::after
  var fox, timeout = 120;

  describe('positional pseudo class selectors', function() {
    beforeEach(function() {
      fox = new Focss();
    });

    afterEach(function() {
      fox.destroy();
      fox = null;
    });

    describe(':first-child', function() {
      it('affects only the first child', function(done) {
        var ul = affix('ul li+li+li');
        var first = ul.find('li:first-child'), second = first.next(), last = second.next();
        fox.insert('li:first-child', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        requestAnimationFrame(function() {
          expect(first.css('max-width')).toBe('100px');
          expect(second.css('max-width')).toBe('none');
          expect(last.css('max-width')).toBe('none');
          done();
        });
      });

      it('tracks positions', function(done) {
        var ul = affix('ul li+li+li');
        var first = ul.find('li:first-child'), second = first.next(), last = second.next();
        fox.insert('li:first-child', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        ul[0].insertBefore(second[0], first[0]);

        setTimeout(function() {
          expect(first.css('max-width')).toBe('none');
          expect(second.css('max-width')).toBe('100px');
          expect(last.css('max-width')).toBe('none');
          done();
        }, timeout);
      });
    });

    describe(':last-child', function() {
      it('affects only the last child', function(done) {
        var ul = affix('ul li+li+li');
        var last = ul.find('li:last-child'), second = last.prev(), first = second.prev();
        fox.insert('li:last-child', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        requestAnimationFrame(function() {
          expect(last.css('max-width')).toBe('100px');
          expect(second.css('max-width')).toBe('none');
          expect(first.css('max-width')).toBe('none');
          done();
        });
      });

      it('tracks positions', function(done) {
        var ul = affix('ul li+li+li');
        var last = ul.find('li:last-child'), second = last.prev(), first = second.prev();
        fox.insert('li:last-child', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        ul[0].appendChild(second[0]);

        setTimeout(function() {
          expect(last.css('max-width')).toBe('none');
          expect(second.css('max-width')).toBe('100px');
          expect(first.css('max-width')).toBe('none');
          done();
        }, timeout);
      });
    });

    describe(':first-of-type', function() {
      it('affects only the first of tag type', function(done) {
        var ul = affix('section div+span+span+h1');
        var target = $(ul[0].querySelector('span:first-of-type'));

        fox.insert('span:first-of-type', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        requestAnimationFrame(function() {
          expect(target.css('max-width')).toBe('100px');
          expect(target.siblings().css('max-width')).toBe('none');
          done();
        });
      });

      it('tracks positions', function(done) {
        var ul = affix('section div+span+span+h1');
        var target = $(ul[0].querySelector('span:first-of-type'));

        fox.insert('span:first-of-type', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        var span = document.createElement('span');
        ul[0].insertBefore(span, ul[0].childNodes[0]);

        setTimeout(function() {
          expect(target.css('max-width')).toBe('none');
          expect($(span).css('max-width')).toBe('100px');
          done();
        }, timeout);
      });
    });

    describe(':last-of-type', function() {
      it('affects only the last of tag type', function(done) {
        var ul = affix('section div+span+span+h1');
        var target = $(ul[0].querySelector('span:last-of-type'));

        fox.insert('span:last-of-type', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        requestAnimationFrame(function() {
          expect(target.css('max-width')).toBe('100px');
          expect(target.siblings().css('max-width')).toBe('none');
          done();
        });
      });

      it('tracks positions', function(done) {
        var ul = affix('section div+span+span+h1');
        var target = $(ul[0].querySelector('span:last-of-type'));

        fox.insert('span:last-of-type', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        var span = document.createElement('span');
        ul[0].appendChild(span);

        setTimeout(function() {
          expect(target.css('max-width')).toBe('none');
          expect($(span).css('max-width')).toBe('100px');
          done();
        }, timeout);
      });
    });

    describe(':only-of-type', function() {
      it('affects only the last of tag type', function(done) {
        var ul = affix('section div+span+span+h1');
        var target = $(ul[0].querySelector('div:only-of-type'));

        fox.insert('div:only-of-type', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        requestAnimationFrame(function() {
          expect(target.css('max-width')).toBe('100px');
          expect(target.siblings().css('max-width')).toBe('none');
          done();
        });
      });

      it('tracks positions', function(done) {
        var ul = affix('section div+span+span+h1');
        var target = $(ul[0].querySelector('div:only-of-type'));

        fox.insert('div:only-of-type', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        setTimeout(function() {
          expect(target.css('max-width')).toBe('100px');
          var div = document.createElement('div');
          ul[0].appendChild(div);

          setTimeout(function() {
            expect(target.css('max-width')).toBe('none');
            expect($(div).css('max-width')).toBe('none');
            done();
          }, timeout);
        }, timeout);
      });
    });

    describe(':nth-child()', function() {
      it('affects only the nth element', function(done) {
        var ul = affix('ul li+li+li+li');

        fox.insert('li:nth-child(2n)', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        requestAnimationFrame(function() {
          expect(ul.find('li:eq(0)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(1)').css('max-width')).toBe('100px');
          expect(ul.find('li:eq(2)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(3)').css('max-width')).toBe('100px');
          done();
        });
      });

      it('tracks when new element shifts positions', function(done) {
        var ul = affix('ul li+li+li+li');

        fox.insert('li:nth-child(2n)', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        ul[0].insertBefore(document.createElement('li'), ul[0].childNodes[0]);

        setTimeout(function() {
          expect(ul.find('li:eq(0)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(1)').css('max-width')).toBe('100px');
          expect(ul.find('li:eq(2)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(3)').css('max-width')).toBe('100px');
          expect(ul.find('li:eq(4)').css('max-width')).toBe('none');
          done();
        }, timeout);
      });

      it('tracks when positions shift', function(done) {
        var ul = affix('ul li+li+li+li');

        fox.insert('li:nth-child(2n)', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        ul[0].insertBefore(ul[0].childNodes[1], ul[0].childNodes[0]);

        setTimeout(function() {
          expect(ul.find('li:eq(0)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(1)').css('max-width')).toBe('100px');
          expect(ul.find('li:eq(2)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(3)').css('max-width')).toBe('100px');
          done();
        }, timeout);
      });
    });

    describe(':nth-last-child()', function() {
      it('affects only the nth element in reverse', function(done) {
        var ul = affix('ul li+li+li+li');

        fox.insert('li:nth-last-child(2n)', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        requestAnimationFrame(function() {
          expect(ul.find('li:eq(0)').css('max-width')).toBe('100px');
          expect(ul.find('li:eq(1)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(2)').css('max-width')).toBe('100px');
          expect(ul.find('li:eq(3)').css('max-width')).toBe('none');
          done();
        });
      });

      it('tracks when new element shifts positions', function(done) {
        var ul = affix('ul li+li+li+li');

        fox.insert('li:nth-child(2n)', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        ul[0].appendChild(document.createElement('li'));

        setTimeout(function() {
          expect(ul.find('li:eq(0)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(1)').css('max-width')).toBe('100px');
          expect(ul.find('li:eq(2)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(3)').css('max-width')).toBe('100px');
          expect(ul.find('li:eq(4)').css('max-width')).toBe('none');
          done();
        }, timeout);
      });
    });
  });

  describe('relational pseudo class selectors', function() {
    beforeEach(function() {
      fox = new Focss();
    });

    afterEach(function() {
      fox.destroy();
      fox = null;
    });

    describe(':empty', function() {
      it('matches only empty elements', function(done) {
        var ul = affix('ul');

        fox.insert('ul:empty', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        requestAnimationFrame(function() {
          expect(ul.css('max-width')).toBe('100px');

          ul[0].appendChild(document.createElement('li'));

          setTimeout(function() {
            expect(ul.css('max-width')).toBe('none');
            done();
          }, timeout);
        });
      });
    });
  });

  /*
  describe('input pseudo class selectors', function() {
    // IMPORTANT: :visited is impossible due to security restrictions
    describe(':link', function() {});
    describe(':active', function() {});
    describe(':hover', function() {});
    describe(':focus', function() {});
    describe(':enabled', function() {});
    describe(':disabled', function() {});
    describe(':checked', function() {});
    describe(':indeterminate', function() {});
  });
 */
});
<commit_msg>Pseudo: fixing typo<commit_after>define(['index', 'src/eltable'], function(Focss, eltable) {
  // IMPORTANT: We can't do pseudo-elements!
  // e.g. ::first-letter, ::before, ::after
  var fox, timeout = 120;

  describe('positional pseudo class selectors', function() {
    beforeEach(function() {
      fox = new Focss();
    });

    afterEach(function() {
      fox.destroy();
      fox = null;
    });

    describe(':first-child', function() {
      it('affects only the first child', function(done) {
        var ul = affix('ul li+li+li');
        var first = ul.find('li:first-child'), second = first.next(), last = second.next();
        fox.insert('li:first-child', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        requestAnimationFrame(function() {
          expect(first.css('max-width')).toBe('100px');
          expect(second.css('max-width')).toBe('none');
          expect(last.css('max-width')).toBe('none');
          done();
        });
      });

      it('tracks positions', function(done) {
        var ul = affix('ul li+li+li');
        var first = ul.find('li:first-child'), second = first.next(), last = second.next();
        fox.insert('li:first-child', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        ul[0].insertBefore(second[0], first[0]);

        setTimeout(function() {
          expect(first.css('max-width')).toBe('none');
          expect(second.css('max-width')).toBe('100px');
          expect(last.css('max-width')).toBe('none');
          done();
        }, timeout);
      });
    });

    describe(':last-child', function() {
      it('affects only the last child', function(done) {
        var ul = affix('ul li+li+li');
        var last = ul.find('li:last-child'), second = last.prev(), first = second.prev();
        fox.insert('li:last-child', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        requestAnimationFrame(function() {
          expect(last.css('max-width')).toBe('100px');
          expect(second.css('max-width')).toBe('none');
          expect(first.css('max-width')).toBe('none');
          done();
        });
      });

      it('tracks positions', function(done) {
        var ul = affix('ul li+li+li');
        var last = ul.find('li:last-child'), second = last.prev(), first = second.prev();
        fox.insert('li:last-child', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        ul[0].appendChild(second[0]);

        setTimeout(function() {
          expect(last.css('max-width')).toBe('none');
          expect(second.css('max-width')).toBe('100px');
          expect(first.css('max-width')).toBe('none');
          done();
        }, timeout);
      });
    });

    describe(':first-of-type', function() {
      it('affects only the first of tag type', function(done) {
        var ul = affix('section div+span+span+h1');
        var target = $(ul[0].querySelector('span:first-of-type'));

        fox.insert('span:first-of-type', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        requestAnimationFrame(function() {
          expect(target.css('max-width')).toBe('100px');
          expect(target.siblings().css('max-width')).toBe('none');
          done();
        });
      });

      it('tracks positions', function(done) {
        var ul = affix('section div+span+span+h1');
        var target = $(ul[0].querySelector('span:first-of-type'));

        fox.insert('span:first-of-type', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        var span = document.createElement('span');
        ul[0].insertBefore(span, ul[0].childNodes[0]);

        setTimeout(function() {
          expect(target.css('max-width')).toBe('none');
          expect($(span).css('max-width')).toBe('100px');
          done();
        }, timeout);
      });
    });

    describe(':last-of-type', function() {
      it('affects only the last of tag type', function(done) {
        var ul = affix('section div+span+span+h1');
        var target = $(ul[0].querySelector('span:last-of-type'));

        fox.insert('span:last-of-type', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        requestAnimationFrame(function() {
          expect(target.css('max-width')).toBe('100px');
          expect(target.siblings().css('max-width')).toBe('none');
          done();
        });
      });

      it('tracks positions', function(done) {
        var ul = affix('section div+span+span+h1');
        var target = $(ul[0].querySelector('span:last-of-type'));

        fox.insert('span:last-of-type', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        var span = document.createElement('span');
        ul[0].appendChild(span);

        setTimeout(function() {
          expect(target.css('max-width')).toBe('none');
          expect($(span).css('max-width')).toBe('100px');
          done();
        }, timeout);
      });
    });

    describe(':only-of-type', function() {
      it('affects only the last of tag type', function(done) {
        var ul = affix('section div+span+span+h1');
        var target = $(ul[0].querySelector('div:only-of-type'));

        fox.insert('div:only-of-type', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        requestAnimationFrame(function() {
          expect(target.css('max-width')).toBe('100px');
          expect(target.siblings().css('max-width')).toBe('none');
          done();
        });
      });

      it('tracks positions', function(done) {
        var ul = affix('section div+span+span+h1');
        var target = $(ul[0].querySelector('div:only-of-type'));

        fox.insert('div:only-of-type', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        setTimeout(function() {
          expect(target.css('max-width')).toBe('100px');
          var div = document.createElement('div');
          ul[0].appendChild(div);

          setTimeout(function() {
            expect(target.css('max-width')).toBe('none');
            expect($(div).css('max-width')).toBe('none');
            done();
          }, timeout);
        }, timeout);
      });
    });

    describe(':nth-child()', function() {
      it('affects only the nth element', function(done) {
        var ul = affix('ul li+li+li+li');

        fox.insert('li:nth-child(2n)', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        requestAnimationFrame(function() {
          expect(ul.find('li:eq(0)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(1)').css('max-width')).toBe('100px');
          expect(ul.find('li:eq(2)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(3)').css('max-width')).toBe('100px');
          done();
        });
      });

      it('tracks when new element shifts positions', function(done) {
        var ul = affix('ul li+li+li+li');

        fox.insert('li:nth-child(2n)', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        ul[0].insertBefore(document.createElement('li'), ul[0].childNodes[0]);

        setTimeout(function() {
          expect(ul.find('li:eq(0)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(1)').css('max-width')).toBe('100px');
          expect(ul.find('li:eq(2)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(3)').css('max-width')).toBe('100px');
          expect(ul.find('li:eq(4)').css('max-width')).toBe('none');
          done();
        }, timeout);
      });

      it('tracks when positions shift', function(done) {
        var ul = affix('ul li+li+li+li');

        fox.insert('li:nth-child(2n)', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        ul[0].insertBefore(ul[0].childNodes[1], ul[0].childNodes[0]);

        setTimeout(function() {
          expect(ul.find('li:eq(0)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(1)').css('max-width')).toBe('100px');
          expect(ul.find('li:eq(2)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(3)').css('max-width')).toBe('100px');
          done();
        }, timeout);
      });
    });

    describe(':nth-last-child()', function() {
      it('affects only the nth element in reverse', function(done) {
        var ul = affix('ul li+li+li+li');

        fox.insert('li:nth-last-child(2n)', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        requestAnimationFrame(function() {
          expect(ul.find('li:eq(0)').css('max-width')).toBe('100px');
          expect(ul.find('li:eq(1)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(2)').css('max-width')).toBe('100px');
          expect(ul.find('li:eq(3)').css('max-width')).toBe('none');
          done();
        });
      });

      it('tracks when new element shifts positions', function(done) {
        var ul = affix('ul li+li+li+li');

        fox.insert('li:nth-last-child(2n)', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        ul[0].appendChild(document.createElement('li'));

        setTimeout(function() {
          expect(ul.find('li:eq(0)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(1)').css('max-width')).toBe('100px');
          expect(ul.find('li:eq(2)').css('max-width')).toBe('none');
          expect(ul.find('li:eq(3)').css('max-width')).toBe('100px');
          expect(ul.find('li:eq(4)').css('max-width')).toBe('none');
          done();
        }, timeout);
      });
    });
  });

  describe('relational pseudo class selectors', function() {
    beforeEach(function() {
      fox = new Focss();
    });

    afterEach(function() {
      fox.destroy();
      fox = null;
    });

    describe(':empty', function() {
      it('matches only empty elements', function(done) {
        var ul = affix('ul');

        fox.insert('ul:empty', { 'max-width': 'foo' });
        fox.process({ foo: 100 });

        requestAnimationFrame(function() {
          expect(ul.css('max-width')).toBe('100px');

          ul[0].appendChild(document.createElement('li'));

          setTimeout(function() {
            expect(ul.css('max-width')).toBe('none');
            done();
          }, timeout);
        });
      });
    });
  });

  /*
  describe('input pseudo class selectors', function() {
    // IMPORTANT: :visited is impossible due to security restrictions
    describe(':link', function() {});
    describe(':active', function() {});
    describe(':hover', function() {});
    describe(':focus', function() {});
    describe(':enabled', function() {});
    describe(':disabled', function() {});
    describe(':checked', function() {});
    describe(':indeterminate', function() {});
  });
 */
});
<|endoftext|>


<commit_before>var Bussholdeplass    = require('./Components/Bussholdeplass')
, Departure           = require('./Components/Departure')
, FavoriteHandler     = require('./Components/FavoriteHandler')
, Stops               = require('./Components/Stops');

var env               = require('FuseJS/Environment')
, Observable          = require('FuseJS/Observable')
, GeoLocation         = require('FuseJS/GeoLocation');

var departures        = Observable()
, favorites           = Observable()
, favorite_departures = Observable()
, filtered_view       = Observable()
, stop_info           = Observable()
, stop_search         = Observable('');

var isFav             = Observable(false);
var isLoading         = Observable(false);
var hasLocation       = Observable(false);

function endLoading() {
  isLoading.value = false;
}

function reloadHandler() {
  isLoading.value = true;
  load_data();
}

/* Func
-----------------------------------------------------------------------------*/
var ApiReq = {
  url: 'http://bybussen.api.tmn.io/',
  get: function (path, callback) {
    return fetch(ApiReq.url + path, {
      method: 'GET',
      headers: { 'Content-Type': 'application/json' }
    }).then(function (response) {
      return response.json();
    })
  }
};

var go_back = function () {
  setTimeout(function () {
    departures.clear();
  }, 250)
};

var stop_clicked = function (args) {
  stop_info.value = JSON.parse(JSON.stringify(args.data));
  stop_info.value.displayName = stop_info.value.name; //.length > 30 ? stop_info.value.name.toUpperCase().substring(0, 28) + ' ...' : stop_info.value.name.toUpperCase();
  isFav.value = FavoriteHandler.hasFavorite(stop_info.value.id);
  load_data();
};

var load_data = function () {
  ApiReq.get('rt/' + stop_info.value.locationId).then(function (responseObject) {
    responseObject.locationId = responseObject.name.match(/(\d+){4}/g)[0];
    var newDeps = responseObject['next'].map(function (e) {
      return new Departure(e.l, e.t, e.ts, e.rt, e.d);
    });

    if (responseObject.locationId == stop_info.value.locationId) {
      setTimeout(function () {
        departures.replaceAll(newDeps);
        endLoading();
      }, 250);
    }
  });
};

/* Load favorites
-----------------------------------------------------------------------------*/
function load_fav_data() {
  var favs = FavoriteHandler.getFavoriteList();
  var newFavs = [];

  favs.forEach(function (stop) {
    var stopDep = {
      name: stop.name,
      direction: stop.direction,
      departures: new Observable(),
      data: stop
    };

    load_fav_departures(stopDep.departures, stop.locationId)

    newFavs.push(stopDep);
  });

  favorite_departures.replaceAll(newFavs);
}

function load_fav_departures(arr, id) {
  ApiReq.get('rt/' + id).then(function (responseObject) {
    var departures = responseObject.next.slice(0, 4).map(function (d) {
      return new Departure(d.l, d.t, d.ts, d.rt, d.d);
    });

    arr.replaceAll(departures);
  })
  .catch(function (err) {
    console.log('ERROR: ' + err.message);
  });
}

function reload_favs() {
  favorite_departures._values.forEach(function (e) {
    load_fav_departures(e.departures, e.data.locationId);
  });
}

function delete_favorite(args) {
  favorites.remove(args.data);

  setTimeout(function () {
    FavoriteHandler.deleteFavorite(args.data.id);
    load_fav_data();
  }, 1150);

}

function add_favorite() {
  if (FavoriteHandler.hasFavorite(stop_info.value.id)) {
    FavoriteHandler.deleteFavorite(stop_info.value.id);
  }
  else {
    FavoriteHandler.addFavorite(stop_info.value);
  }

  favorites.replaceAll(FavoriteHandler.getFavoriteList());
  isFav.value = FavoriteHandler.hasFavorite(stop_info.value.id);

  load_fav_data();
}

/* Geolocation + nearest stops
-----------------------------------------------------------------------------*/
function update_nearest_stop(location) {
  if (!env.mobile) {
    return;
  }

  if (location === undefined) {
    if (GeoLocation.location !== null) {
      hasLocation.value = true;
      location = GeoLocation.location;
    }
    else {
      hasLocation.value = false;
      return;
    }
  }

  ApiReq.get('stops/nearest/' + location.latitude + '/' + location.longitude).then(function (responseObject) {
    var convertedStops = responseObject.map(function (e) {
      return new Bussholdeplass(e.busStopId, e.locationId, e.name, e.longitude, e.latitude, e.distance);
    });

    filtered_view.replaceAll(convertedStops);
  })
  .catch(function (err) {
    console.log('ERROR: ' + err.message);
  });
}

/* Search typing handler
-----------------------------------------------------------------------------*/
stop_search.addSubscriber(function () {
  var search_string = stop_search.value;
  hasLocation.value = search_string.length > 0 && GeoLocation.location !== null;

  if (search_string.length < 3) {
    if (search_string.length === 0) {
      filtered_view.clear();
      update_nearest_stop();
    }

    return;
  }

  filtered_view.replaceAll(Stops.filter(function (e) {
    return e.name.toUpperCase().indexOf(search_string.toUpperCase()) > -1;
  }));
});

/* Init
-----------------------------------------------------------------------------*/
load_fav_data();
favorites.replaceAll(FavoriteHandler.getFavoriteList());

/* Exports
-----------------------------------------------------------------------------*/
module.exports = {
  departures: departures,
  favorites: favorites,
  favorite_departures: favorite_departures,
  favorite_delete: delete_favorite,
  filtered_view: filtered_view,
  go_back: go_back,
  reload_favs: reload_favs,
  stop_clicked: stop_clicked,
  stop_info: stop_info,
  stop_search: stop_search,

  isFav: isFav,
  isLoading: isLoading,
  reloadHandler: reloadHandler,

  load_data: load_data,
  add_favorite: add_favorite,
  hasLocation: hasLocation
};
<commit_msg>Add error codes to app.js<commit_after>var Bussholdeplass    = require('./Components/Bussholdeplass')
, Departure           = require('./Components/Departure')
, FavoriteHandler     = require('./Components/FavoriteHandler')
, Stops               = require('./Components/Stops');

var env               = require('FuseJS/Environment')
, Observable          = require('FuseJS/Observable')
, GeoLocation         = require('FuseJS/GeoLocation');

var departures        = Observable()
, favorites           = Observable()
, favorite_departures = Observable()
, filtered_view       = Observable()
, stop_info           = Observable()
, stop_search         = Observable('');

var isFav             = Observable(false);
var isLoading         = Observable(false);
var hasLocation       = Observable(false);

function endLoading() {
  isLoading.value = false;
}

function reloadHandler() {
  isLoading.value = true;
  load_data();
}

/* Func
-----------------------------------------------------------------------------*/
var ApiReq = {
  url: 'http://bybussen.api.tmn.io/',
  get: function (path, callback) {
    return fetch(ApiReq.url + path, {
      method: 'GET',
      headers: { 'Content-Type': 'application/json' }
    }).then(function (response) {
      return response.json();
    })
  }
};

var go_back = function () {
  setTimeout(function () {
    departures.clear();
  }, 250)
};

var stop_clicked = function (args) {
  stop_info.value = JSON.parse(JSON.stringify(args.data));
  stop_info.value.displayName = stop_info.value.name; //.length > 30 ? stop_info.value.name.toUpperCase().substring(0, 28) + ' ...' : stop_info.value.name.toUpperCase();
  isFav.value = FavoriteHandler.hasFavorite(stop_info.value.id);
  load_data();
};

var load_data = function () {
  ApiReq.get('rt/' + stop_info.value.locationId).then(function (responseObject) {
    responseObject.locationId = responseObject.name.match(/(\d+){4}/g)[0];
    var newDeps = responseObject['next'].map(function (e) {
      return new Departure(e.l, e.t, e.ts, e.rt, e.d);
    });

    if (responseObject.locationId == stop_info.value.locationId) {
      setTimeout(function () {
        departures.replaceAll(newDeps);
      }, 250);
    }

    setTimeout(function () {
      endLoading();
    }, 250);
  })
  .catch(function (e) {
    console.log('ERROR[2]' + e.message);
  });
};

/* Load favorites
-----------------------------------------------------------------------------*/
function load_fav_data() {
  var favs = FavoriteHandler.getFavoriteList();
  var newFavs = [];

  favs.forEach(function (stop) {
    var stopDep = {
      name: stop.name,
      direction: stop.direction,
      departures: new Observable(),
      data: stop
    };

    load_fav_departures(stopDep.departures, stop.locationId)

    newFavs.push(stopDep);
  });

  favorite_departures.replaceAll(newFavs);
}

function load_fav_departures(arr, id) {
  ApiReq.get('rt/' + id).then(function (responseObject) {
    var departures = responseObject.next.slice(0, 4).map(function (d) {
      return new Departure(d.l, d.t, d.ts, d.rt, d.d);
    });

    arr.replaceAll(departures);
  })
  .catch(function (err) {
    console.log('ERROR[0]: ' + err.message);
  });
}

function reload_favs() {
  favorite_departures._values.forEach(function (e) {
    load_fav_departures(e.departures, e.data.locationId);
  });
}

function delete_favorite(args) {
  favorites.remove(args.data);

  setTimeout(function () {
    FavoriteHandler.deleteFavorite(args.data.id);
    load_fav_data();
  }, 1150);

}

function add_favorite() {
  if (FavoriteHandler.hasFavorite(stop_info.value.id)) {
    FavoriteHandler.deleteFavorite(stop_info.value.id);
  }
  else {
    FavoriteHandler.addFavorite(stop_info.value);
  }

  favorites.replaceAll(FavoriteHandler.getFavoriteList());
  isFav.value = FavoriteHandler.hasFavorite(stop_info.value.id);

  load_fav_data();
}

/* Geolocation + nearest stops
-----------------------------------------------------------------------------*/
function update_nearest_stop(location) {
  if (!env.mobile) {
    return;
  }

  if (location === undefined) {
    if (GeoLocation.location !== null) {
      hasLocation.value = true;
      location = GeoLocation.location;
    }
    else {
      hasLocation.value = false;
      return;
    }
  }

  ApiReq.get('stops/nearest/' + location.latitude + '/' + location.longitude).then(function (responseObject) {
    var convertedStops = responseObject.map(function (e) {
      return new Bussholdeplass(e.busStopId, e.locationId, e.name, e.longitude, e.latitude, e.distance);
    });

    filtered_view.replaceAll(convertedStops);
  })
  .catch(function (err) {
    console.log('ERROR[1]: ' + err.message);
  });
}

/* Search typing handler
-----------------------------------------------------------------------------*/
stop_search.addSubscriber(function () {
  var search_string = stop_search.value;
  hasLocation.value = search_string.length > 0 && GeoLocation.location !== null;

  if (search_string.length < 3) {
    if (search_string.length === 0) {
      filtered_view.clear();
      update_nearest_stop();
    }

    return;
  }

  filtered_view.replaceAll(Stops.filter(function (e) {
    return e.name.toUpperCase().indexOf(search_string.toUpperCase()) > -1;
  }));
});

/* Init
-----------------------------------------------------------------------------*/
load_fav_data();
favorites.replaceAll(FavoriteHandler.getFavoriteList());

/* Exports
-----------------------------------------------------------------------------*/
module.exports = {
  departures: departures,
  favorites: favorites,
  favorite_departures: favorite_departures,
  favorite_delete: delete_favorite,
  filtered_view: filtered_view,
  go_back: go_back,
  reload_favs: reload_favs,
  stop_clicked: stop_clicked,
  stop_info: stop_info,
  stop_search: stop_search,

  isFav: isFav,
  isLoading: isLoading,
  reloadHandler: reloadHandler,

  load_data: load_data,
  add_favorite: add_favorite,
  hasLocation: hasLocation
};
<|endoftext|>


<commit_before>function Npc(Character) {
    this.character = Character;
    this.vector = new Vector;
    this.arrowKeys = [38, 39, 40, 37];

    this.speed = 120; // == in milliseconds
    this.score = document.getElementById("npc-score");
    this.playing = null;
}

Npc.prototype.play = function() {
    var Npc = this;

    Npc.playing = setInterval(function() {
        Npc.move();
        Npc.character.painter.npcPaint();
        Npc.updateScore();
    }, Npc.speed);
};

Npc.prototype.move = function() {
    var Npc = this;
    var direction = Npc.look(Npc.character.element);

    if (direction === false) {
        Npc.moveRandom();
    }

    else {
        Npc.character.move.direction(direction);
    }
};

Npc.prototype.look = function(element) {
    var Npc = this;
    var numOfDirections = Npc.arrowKeys.length;
    var arrowKeys = Util.shuffleArray(Npc.arrowKeys);
    var isPaintable;

    for(var i=0; i < numOfDirections; i++) {
        isPaintable = Npc.isPaintable(element, arrowKeys[i]);

        if (isPaintable) {
            return arrowKeys[i];
        }
    }

    return false;
};

Npc.prototype.isPaintable = function(currentLocationElement, direction) {
    var Npc = this;
    var currentVector = new Vector(currentLocationElement);
    var newVector = Npc.getVector(currentVector, direction);

    if (newVector) {
        return Npc.character.painter.isPaintable(newVector, Npc);
    }

    return false;
};

Npc.prototype.getVector = function(currentVector, direction) {
    switch(direction) {
        case 38:
            return currentVector.north();
        case 39:
            return currentVector.east();
        case 40:
            return currentVector.south();
        case 37:
            return currentVector.west();
    }
};

Npc.prototype.moveRandom = function() {
    var randomDirection = null;
    var Npc = this;

    randomDirection = Util.randomItem(Npc.arrowKeys);
    Npc.character.move.direction(randomDirection);
};

Npc.prototype.currentScore = function() {
    return document.getElementsByClassName("npc-painted").length;
};

Npc.prototype.updateScore = function() {
    var Npc = this;
    Npc.score.innerHTML = Npc.currentScore();
};

Npc.prototype.stop = function() {
    var Npc = this;
    clearInterval(Npc.playing);
};<commit_msg>Slowed down npc a little.<commit_after>function Npc(Character) {
    this.character = Character;
    this.vector = new Vector;
    this.arrowKeys = [38, 39, 40, 37];

    this.speed = 135; // == in milliseconds
    this.score = document.getElementById("npc-score");
    this.playing = null;
}

Npc.prototype.play = function() {
    var Npc = this;

    Npc.playing = setInterval(function() {
        Npc.move();
        Npc.character.painter.npcPaint();
        Npc.updateScore();
    }, Npc.speed);
};

Npc.prototype.move = function() {
    var Npc = this;
    var direction = Npc.look(Npc.character.element);

    if (direction === false) {
        Npc.moveRandom();
    }

    else {
        Npc.character.move.direction(direction);
    }
};

Npc.prototype.look = function(element) {
    var Npc = this;
    var numOfDirections = Npc.arrowKeys.length;
    var arrowKeys = Util.shuffleArray(Npc.arrowKeys);
    var isPaintable;

    for(var i=0; i < numOfDirections; i++) {
        isPaintable = Npc.isPaintable(element, arrowKeys[i]);

        if (isPaintable) {
            return arrowKeys[i];
        }
    }

    return false;
};

Npc.prototype.isPaintable = function(currentLocationElement, direction) {
    var Npc = this;
    var currentVector = new Vector(currentLocationElement);
    var newVector = Npc.getVector(currentVector, direction);

    if (newVector) {
        return Npc.character.painter.isPaintable(newVector, Npc);
    }

    return false;
};

Npc.prototype.getVector = function(currentVector, direction) {
    switch(direction) {
        case 38:
            return currentVector.north();
        case 39:
            return currentVector.east();
        case 40:
            return currentVector.south();
        case 37:
            return currentVector.west();
    }
};

Npc.prototype.moveRandom = function() {
    var randomDirection = null;
    var Npc = this;

    randomDirection = Util.randomItem(Npc.arrowKeys);
    Npc.character.move.direction(randomDirection);
};

Npc.prototype.currentScore = function() {
    return document.getElementsByClassName("npc-painted").length;
};

Npc.prototype.updateScore = function() {
    var Npc = this;
    Npc.score.innerHTML = Npc.currentScore();
};

Npc.prototype.stop = function() {
    var Npc = this;
    clearInterval(Npc.playing);
};<|endoftext|>


<commit_before>import {Span, Node, nodeTypes, Pos, style, spanAtOrBefore} from "../model"
import {joinPoint} from "../transform"

import {charCategory} from "./char"

const commands = Object.create(null)

export function registerCommand(name, func) {
  commands[name] = func
}

export function execCommand(pm, name) {
  let ext = pm.input.commandExtensions[name]
  if (ext && ext.high) for (let i = 0; i < ext.high.length; i++)
    if (ext.high[i](pm) !== false) return true
  if (ext && ext.normal) for (let i = 0; i < ext.normal.length; i++)
    if (ext.normal[i](pm) !== false) return true
  let base = commands[name]
  if (base && base(pm) !== false) return true
  if (ext && ext.low) for (let i = 0; i < ext.low.length; i++)
    if (ext.low[i](pm) !== false) return true
  return false
}

function clearSel(pm) {
  let sel = pm.selection, tr = pm.tr
  if (!sel.empty) tr.delete(sel.from, sel.to)
  return tr
}

commands.insertHardBreak = pm => {
  pm.scrollIntoView()
  let tr = clearSel(pm), pos = pm.selection.from
  if (pm.doc.path(pos.path).type == nodeTypes.code_block)
    tr.insertText(pos, "\n")
  else
    tr.insert(pos, new Span("hard_break"))
  return pm.apply(tr)
}

commands.setStrong = pm => pm.setInlineStyle(style.strong, true)
commands.unsetStrong = pm => pm.setInlineStyle(style.strong, false)
commands.toggleStrong = pm => pm.setInlineStyle(style.strong, null)

commands.setEm = pm => pm.setInlineStyle(style.em, true)
commands.unsetEm = pm => pm.setInlineStyle(style.em, false)
commands.toggleEm = pm => pm.setInlineStyle(style.em, null)

commands.setCode = pm => pm.setInlineStyle(style.code, true)
commands.unsetCode = pm => pm.setInlineStyle(style.code, false)
commands.toggleCode = pm => pm.setInlineStyle(style.code, null)

function blockBefore(pos) {
  for (let i = pos.path.length - 1; i >= 0; i--) {
    let offset = pos.path[i] - 1
    if (offset >= 0) return new Pos(pos.path.slice(0, i), offset)
  }
}

function delBlockBackward(pm, tr, pos) {
  if (pos.depth == 1) { // Top level block, join with block above
    let iBefore = Pos.before(pm.doc, new Pos([], pos.path[0]))
    let bBefore = blockBefore(pos)
    if (iBefore && bBefore) {
      if (iBefore.cmp(bBefore) > 0) bBefore = null
      else iBefore = null
    }
    if (iBefore) {
      tr.delete(iBefore, pos)
      let joinable = joinPoint(tr.doc, tr.map(pos).pos, 1)
      if (joinable) tr.join(joinable)
    } else if (bBefore) {
      tr.delete(bBefore, bBefore.shift(1))
    }
  } else {
    let last = pos.depth - 1
    let parent = pm.doc.path(pos.path.slice(0, last))
    let offset = pos.path[last]
    // Top of list item below other list item
    // Join with the one above
    if (parent.type == nodeTypes.list_item &&
        offset == 0 && pos.path[last - 1] > 0)
      tr.join(joinPoint(pm.doc, pos))
    // Any other nested block, lift up
    else
      tr.lift(pos, pos)
  }
}

function moveBackward(parent, offset, by) {
  if (by == "char") return offset - 1
  if (by == "word") {
    let {offset: nodeOffset, innerOffset} = spanAtOrBefore(parent, offset)
    let cat = null, counted = 0
    for (; nodeOffset >= 0; nodeOffset--, innerOffset = null) {
      let child = parent.content[nodeOffset], size = child.size
      if (child.type != nodeTypes.text) return cat ? offset : offset - 1

      for (let i = innerOffset == null ? size : innerOffset; i > 0; i--) {
        let nextCharCat = charCategory(child.text.charAt(i - 1))
        if (cat == null || counted == 1 && cat == "space") cat = nextCharCat
        else if (cat != nextCharCat) return offset
        offset--
        counted++
      }
    }
    return offset
  }
  throw new Error("Unknown motion unit: " + by)
}

function delBackward(pm, by) {
  pm.scrollIntoView()

  let tr = pm.tr, sel = pm.selection, from = sel.from
  if (!sel.empty)
    tr.delete(from, sel.to)
  else if (from.offset == 0)
    delBlockBackward(pm, tr, from)
  else
    tr.delete(new Pos(from.path, moveBackward(pm.doc.path(from.path), from.offset, by)), from)
  return pm.apply(tr)
}

commands.delBackward = pm => delBackward(pm, "char")

commands.delWordBackward = pm => delBackward(pm, "word")

function blockAfter(doc, pos) {
  let path = pos.path
  while (path.length > 0) {
    let end = path.length - 1
    let offset = path[end] + 1
    path = path.slice(0, end)
    let node = doc.path(path)
    if (offset < node.content.length)
      return new Pos(path, offset)
  }
}

function delBlockForward(pm, tr, pos) {
  let lst = pos.depth - 1
  let iAfter = Pos.after(pm.doc, new Pos(pos.path.slice(0, lst), pos.path[lst] + 1))
  let bAfter = blockAfter(pm.doc, pos)
  if (iAfter && bAfter) {
    if (iAfter.cmp(bAfter.shift(1)) < 0) bAfter = null
    else iAfter = null
  }

  if (iAfter) {
    tr.delete(pos, iAfter)
    let joinable = joinPoint(tr.doc, tr.map(pos).pos, 1)
    if (joinable) tr.join(joinable)
  } else if (bAfter) {
    tr.delete(bAfter, bAfter.shift(1))
  }
}

function moveForward(parent, offset, by) {
  if (by == "char") return offset + 1
  if (by == "word") {
    let {offset: nodeOffset, innerOffset} = spanAtOrBefore(parent, offset)
    let cat = null, counted = 0
    for (; nodeOffset < parent.content.length; nodeOffset++, innerOffset = 0) {
      let child = parent.content[nodeOffset], size = child.size
      if (child.type != nodeTypes.text) return cat ? offset : offset + 1

      for (let i = innerOffset; i < size; i++) {
        let nextCharCat = charCategory(child.text.charAt(i))
        if (cat == null || counted == 1 && cat == "space") cat = nextCharCat
        else if (cat != nextCharCat) return offset
        offset++
        counted++
      }
    }
    return offset
  }
  throw new Error("Unknown motion unit: " + by)
}

function delForward(pm, by) {
  pm.scrollIntoView()
  let tr = pm.tr, sel = pm.selection, from = sel.from
  if (!sel.empty) {
    tr.delete(from, sel.to)
  } else {
    let parent = pm.doc.path(from.path)
    if (from.offset == parent.size)
      delBlockForward(pm, tr, from)
    else
      tr.delete(from, new Pos(from.path, moveForward(parent, from.offset, by)))
  }
  return pm.apply(tr)
}

commands.delForward = pm => delForward(pm, "char")

commands.delWordForward = pm => delForward(pm, "word")

function scrollAnd(pm, value) {
  pm.scrollIntoView()
  return value
}

commands.undo = pm => scrollAnd(pm, pm.history.undo())
commands.redo = pm => scrollAnd(pm, pm.history.redo())

commands.join = pm => {
  let point = joinPoint(pm.doc, pm.selection.head)
  if (!point) return false
  return pm.apply(pm.tr.join(point))
}

commands.lift = pm => {
  let sel = pm.selection
  let result = pm.apply(pm.tr.lift(sel.from, sel.to))
  if (result !== false) pm.scrollIntoView()
  return result
}

function wrap(pm, type) {
  let sel = pm.selection
  pm.scrollIntoView()
  return pm.apply(pm.tr.wrap(sel.from, sel.to, new Node(type)))
}

commands.wrapBulletList = pm => wrap(pm, "bullet_list")
commands.wrapOrderedList = pm => wrap(pm, "ordered_list")
commands.wrapBlockquote = pm => wrap(pm, "blockquote")

commands.endBlock = pm => {
  pm.scrollIntoView()
  let pos = pm.selection.from
  let tr = clearSel(pm)
  let block = pm.doc.path(pos.path)
  if (pos.depth > 1 && block.content.length == 0 &&
      tr.lift(pos).steps.length) {
    // Lift
  } else if (block.type == nodeTypes.code_block && pos.offset < block.size) {
    tr.insertText(pos, "\n")
  } else {
    let end = pos.depth - 1
    let isList = end > 0 && pos.path[end] == 0 &&
        pm.doc.path(pos.path.slice(0, end)).type == nodeTypes.list_item
    let type = pos.offset == block.size ? new Node("paragraph") : null
    tr.split(pos, isList ? 2 : 1, type)
  }
  return pm.apply(tr)
}

function setType(pm, type, attrs) {
  let sel = pm.selection
  pm.scrollIntoView()
  return pm.apply(pm.tr.setBlockType(sel.from, sel.to, new Node(type, attrs)))
}

commands.makeH1 = pm => setType(pm, "heading", {level: 1})
commands.makeH2 = pm => setType(pm, "heading", {level: 2})
commands.makeH3 = pm => setType(pm, "heading", {level: 3})
commands.makeH4 = pm => setType(pm, "heading", {level: 4})
commands.makeH5 = pm => setType(pm, "heading", {level: 5})
commands.makeH6 = pm => setType(pm, "heading", {level: 6})

commands.makeParagraph = pm => setType(pm, "paragraph")
commands.makeCodeBlock = pm => setType(pm, "code_block")

function insertOpaqueBlock(pm, type, attrs) {
  type = nodeTypes[type]
  pm.scrollIntoView()
  let pos = pm.selection.from
  let tr = clearSel(pm)
  let parent = tr.doc.path(pos.path)
  if (parent.type.type != type.type) return false
  let off = 0
  if (pos.offset) {
    tr.split(pos)
    off = 1
  }
  return pm.apply(tr.insert(pos.shorten(null, off), new Node(type, attrs)))
}

commands.insertRule = pm => insertOpaqueBlock(pm, "horizontal_rule")
<commit_msg>Make backspace split list items when backing into them<commit_after>import {Span, Node, nodeTypes, Pos, style, spanAtOrBefore} from "../model"
import {joinPoint} from "../transform"

import {charCategory} from "./char"

const commands = Object.create(null)

export function registerCommand(name, func) {
  commands[name] = func
}

export function execCommand(pm, name) {
  let ext = pm.input.commandExtensions[name]
  if (ext && ext.high) for (let i = 0; i < ext.high.length; i++)
    if (ext.high[i](pm) !== false) return true
  if (ext && ext.normal) for (let i = 0; i < ext.normal.length; i++)
    if (ext.normal[i](pm) !== false) return true
  let base = commands[name]
  if (base && base(pm) !== false) return true
  if (ext && ext.low) for (let i = 0; i < ext.low.length; i++)
    if (ext.low[i](pm) !== false) return true
  return false
}

function clearSel(pm) {
  let sel = pm.selection, tr = pm.tr
  if (!sel.empty) tr.delete(sel.from, sel.to)
  return tr
}

commands.insertHardBreak = pm => {
  pm.scrollIntoView()
  let tr = clearSel(pm), pos = pm.selection.from
  if (pm.doc.path(pos.path).type == nodeTypes.code_block)
    tr.insertText(pos, "\n")
  else
    tr.insert(pos, new Span("hard_break"))
  return pm.apply(tr)
}

commands.setStrong = pm => pm.setInlineStyle(style.strong, true)
commands.unsetStrong = pm => pm.setInlineStyle(style.strong, false)
commands.toggleStrong = pm => pm.setInlineStyle(style.strong, null)

commands.setEm = pm => pm.setInlineStyle(style.em, true)
commands.unsetEm = pm => pm.setInlineStyle(style.em, false)
commands.toggleEm = pm => pm.setInlineStyle(style.em, null)

commands.setCode = pm => pm.setInlineStyle(style.code, true)
commands.unsetCode = pm => pm.setInlineStyle(style.code, false)
commands.toggleCode = pm => pm.setInlineStyle(style.code, null)

function blockBefore(pos) {
  for (let i = pos.path.length - 1; i >= 0; i--) {
    let offset = pos.path[i] - 1
    if (offset >= 0) return new Pos(pos.path.slice(0, i), offset)
  }
}

function delBlockBackward(pm, tr, pos) {
  if (pos.depth == 1) { // Top level block, join with block above
    let iBefore = Pos.before(pm.doc, new Pos([], pos.path[0]))
    let bBefore = blockBefore(pos)
    if (iBefore && bBefore) {
      if (iBefore.cmp(bBefore) > 0) bBefore = null
      else iBefore = null
    }
    if (iBefore) {
      tr.delete(iBefore, pos)
      let joinable = joinPoint(tr.doc, tr.map(pos).pos, 1)
      if (joinable) tr.join(joinable)
    } else if (bBefore) {
      tr.delete(bBefore, bBefore.shift(1))
    }
  } else {
    let last = pos.depth - 1
    let parent = pm.doc.path(pos.path.slice(0, last))
    let offset = pos.path[last]
    // Top of list item below other list item
    // Join with the one above
    if (parent.type == nodeTypes.list_item &&
        offset == 0 && pos.path[last - 1] > 0) {
      tr.join(joinPoint(pm.doc, pos))
    // Any other nested block, lift up
    } else {
      tr.lift(pos, pos)
      let next = pos.depth - 2
      // Split list item when we backspace back into it
      if (next > 0 && offset > 0 &&
          pm.doc.path(pos.path.slice(0, next)).type == nodeTypes.list_item)
        tr.split(new Pos(pos.path.slice(0, next), pos.path[next] + 1))
    }
  }
}

function moveBackward(parent, offset, by) {
  if (by == "char") return offset - 1
  if (by == "word") {
    let {offset: nodeOffset, innerOffset} = spanAtOrBefore(parent, offset)
    let cat = null, counted = 0
    for (; nodeOffset >= 0; nodeOffset--, innerOffset = null) {
      let child = parent.content[nodeOffset], size = child.size
      if (child.type != nodeTypes.text) return cat ? offset : offset - 1

      for (let i = innerOffset == null ? size : innerOffset; i > 0; i--) {
        let nextCharCat = charCategory(child.text.charAt(i - 1))
        if (cat == null || counted == 1 && cat == "space") cat = nextCharCat
        else if (cat != nextCharCat) return offset
        offset--
        counted++
      }
    }
    return offset
  }
  throw new Error("Unknown motion unit: " + by)
}

function delBackward(pm, by) {
  pm.scrollIntoView()

  let tr = pm.tr, sel = pm.selection, from = sel.from
  if (!sel.empty)
    tr.delete(from, sel.to)
  else if (from.offset == 0)
    delBlockBackward(pm, tr, from)
  else
    tr.delete(new Pos(from.path, moveBackward(pm.doc.path(from.path), from.offset, by)), from)
  return pm.apply(tr)
}

commands.delBackward = pm => delBackward(pm, "char")

commands.delWordBackward = pm => delBackward(pm, "word")

function blockAfter(doc, pos) {
  let path = pos.path
  while (path.length > 0) {
    let end = path.length - 1
    let offset = path[end] + 1
    path = path.slice(0, end)
    let node = doc.path(path)
    if (offset < node.content.length)
      return new Pos(path, offset)
  }
}

function delBlockForward(pm, tr, pos) {
  let lst = pos.depth - 1
  let iAfter = Pos.after(pm.doc, new Pos(pos.path.slice(0, lst), pos.path[lst] + 1))
  let bAfter = blockAfter(pm.doc, pos)
  if (iAfter && bAfter) {
    if (iAfter.cmp(bAfter.shift(1)) < 0) bAfter = null
    else iAfter = null
  }

  if (iAfter) {
    tr.delete(pos, iAfter)
    let joinable = joinPoint(tr.doc, tr.map(pos).pos, 1)
    if (joinable) tr.join(joinable)
  } else if (bAfter) {
    tr.delete(bAfter, bAfter.shift(1))
  }
}

function moveForward(parent, offset, by) {
  if (by == "char") return offset + 1
  if (by == "word") {
    let {offset: nodeOffset, innerOffset} = spanAtOrBefore(parent, offset)
    let cat = null, counted = 0
    for (; nodeOffset < parent.content.length; nodeOffset++, innerOffset = 0) {
      let child = parent.content[nodeOffset], size = child.size
      if (child.type != nodeTypes.text) return cat ? offset : offset + 1

      for (let i = innerOffset; i < size; i++) {
        let nextCharCat = charCategory(child.text.charAt(i))
        if (cat == null || counted == 1 && cat == "space") cat = nextCharCat
        else if (cat != nextCharCat) return offset
        offset++
        counted++
      }
    }
    return offset
  }
  throw new Error("Unknown motion unit: " + by)
}

function delForward(pm, by) {
  pm.scrollIntoView()
  let tr = pm.tr, sel = pm.selection, from = sel.from
  if (!sel.empty) {
    tr.delete(from, sel.to)
  } else {
    let parent = pm.doc.path(from.path)
    if (from.offset == parent.size)
      delBlockForward(pm, tr, from)
    else
      tr.delete(from, new Pos(from.path, moveForward(parent, from.offset, by)))
  }
  return pm.apply(tr)
}

commands.delForward = pm => delForward(pm, "char")

commands.delWordForward = pm => delForward(pm, "word")

function scrollAnd(pm, value) {
  pm.scrollIntoView()
  return value
}

commands.undo = pm => scrollAnd(pm, pm.history.undo())
commands.redo = pm => scrollAnd(pm, pm.history.redo())

commands.join = pm => {
  let point = joinPoint(pm.doc, pm.selection.head)
  if (!point) return false
  return pm.apply(pm.tr.join(point))
}

commands.lift = pm => {
  let sel = pm.selection
  let result = pm.apply(pm.tr.lift(sel.from, sel.to))
  if (result !== false) pm.scrollIntoView()
  return result
}

function wrap(pm, type) {
  let sel = pm.selection
  pm.scrollIntoView()
  return pm.apply(pm.tr.wrap(sel.from, sel.to, new Node(type)))
}

commands.wrapBulletList = pm => wrap(pm, "bullet_list")
commands.wrapOrderedList = pm => wrap(pm, "ordered_list")
commands.wrapBlockquote = pm => wrap(pm, "blockquote")

commands.endBlock = pm => {
  pm.scrollIntoView()
  let pos = pm.selection.from
  let tr = clearSel(pm)
  let block = pm.doc.path(pos.path)
  if (pos.depth > 1 && block.content.length == 0 &&
      tr.lift(pos).steps.length) {
    // Lift
  } else if (block.type == nodeTypes.code_block && pos.offset < block.size) {
    tr.insertText(pos, "\n")
  } else {
    let end = pos.depth - 1
    let isList = end > 0 && pos.path[end] == 0 &&
        pm.doc.path(pos.path.slice(0, end)).type == nodeTypes.list_item
    let type = pos.offset == block.size ? new Node("paragraph") : null
    tr.split(pos, isList ? 2 : 1, type)
  }
  return pm.apply(tr)
}

function setType(pm, type, attrs) {
  let sel = pm.selection
  pm.scrollIntoView()
  return pm.apply(pm.tr.setBlockType(sel.from, sel.to, new Node(type, attrs)))
}

commands.makeH1 = pm => setType(pm, "heading", {level: 1})
commands.makeH2 = pm => setType(pm, "heading", {level: 2})
commands.makeH3 = pm => setType(pm, "heading", {level: 3})
commands.makeH4 = pm => setType(pm, "heading", {level: 4})
commands.makeH5 = pm => setType(pm, "heading", {level: 5})
commands.makeH6 = pm => setType(pm, "heading", {level: 6})

commands.makeParagraph = pm => setType(pm, "paragraph")
commands.makeCodeBlock = pm => setType(pm, "code_block")

function insertOpaqueBlock(pm, type, attrs) {
  type = nodeTypes[type]
  pm.scrollIntoView()
  let pos = pm.selection.from
  let tr = clearSel(pm)
  let parent = tr.doc.path(pos.path)
  if (parent.type.type != type.type) return false
  let off = 0
  if (pos.offset) {
    tr.split(pos)
    off = 1
  }
  return pm.apply(tr.insert(pos.shorten(null, off), new Node(type, attrs)))
}

commands.insertRule = pm => insertOpaqueBlock(pm, "horizontal_rule")
<|endoftext|>


<commit_before>var crypto = require('crypto')

// (String, Number) => String
function sign(sharedKey, offset) {
  offset = offset || 0
  var now = String(Date.now() + offset)
  var shasum = crypto.createHmac('sha256', sharedKey)
  shasum.update(String(now))
  var digest = shasum.digest('hex')
  return new Buffer(now + ':' + digest).toString('base64')
}

// (String, String) => Boolean
function verify(sharedKey, signature) {

  var authStr = new Buffer(signature, 'base64').toString()
  var auth = authStr.split(':')
  var clientNow = parseInt(auth[0])
  var clientHmac = auth[1]

  // check to make sure now matches sorta with server time
  // (+/- 60 seconds)
  var now = Date.now()
  if (Math.abs(now - clientNow) > 60000) {
    throw new Error('clocks not in sync')
  }

  // verify hmac
  var shasum = crypto.createHmac('sha256', sharedKey)
  shasum.update(String(clientNow))
  var hmac = shasum.digest('hex')

  return hmac === clientHmac

}

module.exports = function middleware(sharedKey) {
  return function (req, res, next) {
    try {
      var requestAuthorized = verify(sharedKey, req.headers.authorization)
      if (requestAuthorized) {
        next()
      }
    } catch (e) {
      next(e)
    }
    // request not authorized
    res.statusCode = 403
    res.write(String(Date.now()))
    res.end()
  }
}

module.exports.sign = sign
module.exports.verify = verify<commit_msg>fix middleware flow<commit_after>var crypto = require('crypto')

// (String, Number?) => String
function sign(sharedKey, offset) {
  offset = offset || 0
  var now = String(Date.now() + offset)
  var shasum = crypto.createHmac('sha256', sharedKey)
  shasum.update(String(now))
  var digest = shasum.digest('hex')
  return new Buffer(now + ':' + digest).toString('base64')
}

// (String, String) => Boolean
function verify(sharedKey, signature) {

  var authStr = new Buffer(signature, 'base64').toString()
  var auth = authStr.split(':')
  var clientNow = parseInt(auth[0])
  var clientHmac = auth[1]

  // check to make sure now matches sorta with server time
  // (+/- 60 seconds)
  var now = Date.now()
  if (Math.abs(now - clientNow) > 60000) {
    throw new Error('Clocks not in sync:\n server: ' +
      now + '\n client: ' + clientNow)
  }

  // verify hmac
  var shasum = crypto.createHmac('sha256', sharedKey)
  shasum.update(String(clientNow))
  var hmac = shasum.digest('hex')

  return hmac === clientHmac

}

module.exports = function middleware(sharedKey) {
  return function (req, res, next) {
    try {
      var requestAuthorized = verify(sharedKey, req.headers.authorization)
      if (requestAuthorized) {
        return next()
      } else {
        // request not authorized
        res.statusCode = 403
        res.write(String(Date.now()))
        res.end()
      }
    } catch (e) {
      e.code = 403
      return next(e)
    }
  }
}

module.exports.sign = sign
module.exports.verify = verify<|endoftext|>


<commit_before>import { toString, flow } from 'lodash';
import { format } from '../../../utils/format';

// we use two different frames to make them all essentially pure functions
// main iframe is responsible rendering the preview and is where we proxy the
const mainId = 'fcc-main-frame';
// the test frame is responsible for running the assert tests
const testId = 'fcc-test-frame';

// base tag here will force relative links
// within iframe to point to '' instead of
// append to the current challenge url
// this also allows in-page anchors to work properly
// rather than load another instance of the learn

// window.onerror is added here to catch any errors thrown during the building
// of the frame.
const createHeader = (id = mainId) => `
  <base href='' />
  <script>
    window.__frameId = '${id}';
    window.onerror = function(msg) {
      console.log(msg);
      return true;
    };
    document.addEventListener('click', function(e) {
      let element = e.target;
      while(element && element.nodeName !== 'A') {
        element = element.parentElement;
      }
      if (element) {
        const href = element.getAttribute('href');
        if (!href || href[0] !== '#' && !href.match(/^https?:\\/\\//)) {
          e.preventDefault();
        }
      }
    }, false);
    document.addEventListener('submit', function(e) {
      const action = e.target.getAttribute('action');
      if (!action || !action.match(/https?:\\/\\//)) {
        e.preventDefault();
      }
    }, false);
  </script>
`;

export const runTestInTestFrame = async function(document, test, timeout) {
  const { contentDocument: frame } = document.getElementById(testId);
  return await Promise.race([
    new Promise((_, reject) => setTimeout(() => reject('timeout'), timeout)),
    frame.__runTest(test)
  ]);
};

const createFrame = (document, id) => ctx => {
  const frame = document.createElement('iframe');
  frame.id = id;
  return {
    ...ctx,
    element: frame
  };
};

const hiddenFrameClassName = 'hide-test-frame';
const mountFrame = document => ({ element, ...rest }) => {
  const oldFrame = document.getElementById(element.id);
  if (oldFrame) {
    element.className = oldFrame.className || hiddenFrameClassName;
    oldFrame.parentNode.replaceChild(element, oldFrame);
  } else {
    element.className = hiddenFrameClassName;
    document.body.appendChild(element);
  }
  return {
    ...rest,
    element,
    document: element.contentDocument,
    window: element.contentWindow
  };
};

const buildProxyConsole = proxyLogger => ctx => {
  const oldLog = ctx.window.console.log.bind(ctx.window.console);
  ctx.window.console.log = function proxyConsole(...args) {
    proxyLogger(args.map(arg => format(arg)).join(' '));
    return oldLog(...args);
  };
  return ctx;
};

const initTestFrame = frameReady => ctx => {
  waitForFrame(ctx).then(async () => {
    const { sources, loadEnzyme } = ctx;
    // default for classic challenges
    // should not be used for modern
    const code = sources && 'index' in sources ? sources['index'] : '';
    // provide the file name and get the original source
    const getUserInput = fileName => toString(sources[fileName]);
    await ctx.document.__initTestFrame({ code, getUserInput, loadEnzyme });
    frameReady();
  });
  return ctx;
};

const initMainFrame = (frameReady, proxyLogger) => ctx => {
  waitForFrame(ctx).then(() => {
    // Overwriting the onerror added by createHeader to catch any errors thrown
    // after the frame is ready. It has to be overwritten, as proxyLogger cannot
    // be added as part of createHeader.
    ctx.window.onerror = function(msg) {
      console.log(msg);
      if (proxyLogger) {
        proxyLogger(msg);
      }
      return true;
    };
    frameReady();
  });
  return ctx;
};

const waitForFrame = ctx => {
  return new Promise(resolve => {
    if (ctx.document.readyState === 'loading') {
      ctx.document.addEventListener('DOMContentLoaded', resolve);
    } else {
      resolve();
    }
  });
};

function writeToFrame(content, frame) {
  frame.open();
  frame.write(content);
  frame.close();
  return frame;
}

const writeContentToFrame = ctx => {
  writeToFrame(createHeader(ctx.element.id) + ctx.build, ctx.document);
  return ctx;
};

export const createMainFramer = (document, frameReady, proxyLogger) =>
  createFramer(document, frameReady, proxyLogger, mainId, initMainFrame);

export const createTestFramer = (document, frameReady, proxyLogger) =>
  createFramer(document, frameReady, proxyLogger, testId, initTestFrame);

const createFramer = (document, frameReady, proxyLogger, id, init) =>
  flow(
    createFrame(document, id),
    mountFrame(document),
    buildProxyConsole(proxyLogger),
    writeContentToFrame,
    init(frameReady, proxyLogger)
  );
<commit_msg>fix(client): report cross origin errors (#37793)<commit_after>import { toString, flow } from 'lodash';
import { format } from '../../../utils/format';

// we use two different frames to make them all essentially pure functions
// main iframe is responsible rendering the preview and is where we proxy the
const mainId = 'fcc-main-frame';
// the test frame is responsible for running the assert tests
const testId = 'fcc-test-frame';

// base tag here will force relative links
// within iframe to point to '' instead of
// append to the current challenge url
// this also allows in-page anchors to work properly
// rather than load another instance of the learn

// window.onerror is added here to report any errors thrown during the building
// of the frame.  React dom errors already appear in the console, so onerror
// does not need to pass them on to the default error handler.
const createHeader = (id = mainId) => `
  <base href='' />
  <script>
    window.__frameId = '${id}';
    window.onerror = function(msg) {
      var string = msg.toLowerCase();
      if (string.includes('script error')) {
        msg = 'Build error, open your browser console to learn more.';
      }
      console.log(msg);
      return true;
    };
    document.addEventListener('click', function(e) {
      let element = e.target;
      while(element && element.nodeName !== 'A') {
        element = element.parentElement;
      }
      if (element) {
        const href = element.getAttribute('href');
        if (!href || href[0] !== '#' && !href.match(/^https?:\\/\\//)) {
          e.preventDefault();
        }
      }
    }, false);
    document.addEventListener('submit', function(e) {
      const action = e.target.getAttribute('action');
      if (!action || !action.match(/https?:\\/\\//)) {
        e.preventDefault();
      }
    }, false);
  </script>
`;

export const runTestInTestFrame = async function(document, test, timeout) {
  const { contentDocument: frame } = document.getElementById(testId);
  return await Promise.race([
    new Promise((_, reject) => setTimeout(() => reject('timeout'), timeout)),
    frame.__runTest(test)
  ]);
};

const createFrame = (document, id) => ctx => {
  const frame = document.createElement('iframe');
  frame.id = id;
  return {
    ...ctx,
    element: frame
  };
};

const hiddenFrameClassName = 'hide-test-frame';
const mountFrame = document => ({ element, ...rest }) => {
  const oldFrame = document.getElementById(element.id);
  if (oldFrame) {
    element.className = oldFrame.className || hiddenFrameClassName;
    oldFrame.parentNode.replaceChild(element, oldFrame);
  } else {
    element.className = hiddenFrameClassName;
    document.body.appendChild(element);
  }
  return {
    ...rest,
    element,
    document: element.contentDocument,
    window: element.contentWindow
  };
};

const buildProxyConsole = proxyLogger => ctx => {
  const oldLog = ctx.window.console.log.bind(ctx.window.console);
  ctx.window.console.log = function proxyConsole(...args) {
    proxyLogger(args.map(arg => format(arg)).join(' '));
    return oldLog(...args);
  };
  return ctx;
};

const initTestFrame = frameReady => ctx => {
  waitForFrame(ctx).then(async () => {
    const { sources, loadEnzyme } = ctx;
    // default for classic challenges
    // should not be used for modern
    const code = sources && 'index' in sources ? sources['index'] : '';
    // provide the file name and get the original source
    const getUserInput = fileName => toString(sources[fileName]);
    await ctx.document.__initTestFrame({ code, getUserInput, loadEnzyme });
    frameReady();
  });
  return ctx;
};

const initMainFrame = (frameReady, proxyLogger) => ctx => {
  waitForFrame(ctx).then(() => {
    // Overwriting the onerror added by createHeader to catch any errors thrown
    // after the frame is ready. It has to be overwritten, as proxyLogger cannot
    // be added as part of createHeader.
    ctx.window.onerror = function(msg) {
      var string = msg.toLowerCase();
      if (string.includes('script error')) {
        msg = 'Error, open your browser console to learn more.';
      }
      if (proxyLogger) {
        proxyLogger(msg);
      }
      // let the error propagate so it appears in the browser console, otherwise
      // an error from a cross origin script just appears as 'Script error.'
      return false;
    };
    frameReady();
  });
  return ctx;
};

const waitForFrame = ctx => {
  return new Promise(resolve => {
    if (ctx.document.readyState === 'loading') {
      ctx.document.addEventListener('DOMContentLoaded', resolve);
    } else {
      resolve();
    }
  });
};

function writeToFrame(content, frame) {
  frame.open();
  frame.write(content);
  frame.close();
  return frame;
}

const writeContentToFrame = ctx => {
  writeToFrame(createHeader(ctx.element.id) + ctx.build, ctx.document);
  return ctx;
};

export const createMainFramer = (document, frameReady, proxyLogger) =>
  createFramer(document, frameReady, proxyLogger, mainId, initMainFrame);

export const createTestFramer = (document, frameReady, proxyLogger) =>
  createFramer(document, frameReady, proxyLogger, testId, initTestFrame);

const createFramer = (document, frameReady, proxyLogger, id, init) =>
  flow(
    createFrame(document, id),
    mountFrame(document),
    buildProxyConsole(proxyLogger),
    writeContentToFrame,
    init(frameReady, proxyLogger)
  );
<|endoftext|>


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

const assign = Object.assign;

class CacheEntryDispatcher  {

    constructor(cacheEntryType) {
        this._cacheEntryType = cacheEntryType;
    }

    _cloneObject(object) {
        return assign({}, object);
    }

    _cloneArray(array) {
        return array.slice(0);
    }

    _createCacheEntry(payload) {
        return new CacheEntry(this._cacheEntryType, payload);
    }

    _getCacheEntryId(cacheEntry) {
        return cacheEntry.id;
    }

    dispatch(cacheMap, action) {
        const actionTypeFrags = action.split('_');
        if (actionTypeFrags[0] === 'SET' || actionTypeFrags[0] === 'ADD') {
            if (actionTypeFrags.length >= 4 && actionTypeFrags[3] === 'MANY') {
                const entryCount = action.payload.length;
                for (let i = 0; i < entryCount; i++) {
                    const newCacheEntry = this._createCacheEntry(action.payload[i]);
                    this._updateCacheEntry(newCacheEntry, action.payload[i]);
                    cacheMap.set(this._getCacheEntryId(cacheEntry), cacheEntry);
                }
            }
            else {
                const newCacheEntry = this._createCacheEntry(cacheMap, action.payload);
                this._updateCacheEntry(newCacheEntry, action.payload);
                cacheMap.set(this._getCacheEntryId(cacheEntry), cacheEntry);
            }
        }
        else if (actionTypeFrags[0] === 'UPDATE') {
            const cacheEntry = cacheMap.find(action.id);
            if (cacheEntry) {
                this._updateCacheEntry(cacheEntry, action.payload);
            }
            cacheMap.markAsChanged();
        }
        else if (actionTypeFrags[0] === 'REMOVE') {
            cacheMap.remove(action.id);
        }
    }
}

export default CacheEntryDispatcher;<commit_msg>added missing updateCacheEntry function<commit_after>
import CacheEntry from './CacheEntry';

const assign = Object.assign;

class CacheEntryDispatcher  {

    constructor(cacheEntryType) {
        this._cacheEntryType = cacheEntryType;
    }

    _cloneObject(object) {
        return assign({}, object);
    }

    _cloneArray(array) {
        return array.slice(0);
    }

    _createCacheEntry(payload) {
        return new CacheEntry(this._cacheEntryType, payload);
    }

    _updateCacheEntry(cacheEntry, payload) {
        cacheEntry.update(payload);
        return this;
    }

    _getCacheEntryId(cacheEntry) {
        return cacheEntry.id;
    }

    dispatch(cacheMap, action) {
        const actionTypeFrags = action.split('_');
        if (actionTypeFrags[0] === 'SET' || actionTypeFrags[0] === 'ADD') {
            if (actionTypeFrags.length >= 4 && actionTypeFrags[3] === 'MANY') {
                const entryCount = action.payload.length;
                for (let i = 0; i < entryCount; i++) {
                    const newCacheEntry = this._createCacheEntry(action.payload[i]);
                    newCacheEntry.markAsChanged();
                    cacheMap.set(this._getCacheEntryId(cacheEntry), cacheEntry);
                }
            }
            else {
                const newCacheEntry = this._createCacheEntry(cacheMap, action.payload);
                this._updateCacheEntry(newCacheEntry, action.payload);
                cacheMap.set(this._getCacheEntryId(cacheEntry), cacheEntry);
            }
        }
        else if (actionTypeFrags[0] === 'UPDATE') {
            const cacheEntry = cacheMap.find(action.id);
            if (cacheEntry) {
                this._updateCacheEntry(cacheEntry, action.payload);
            }
            cacheMap.markAsChanged();
        }
        else if (actionTypeFrags[0] === 'REMOVE') {
            cacheMap.remove(action.id);
        }
    }
}

export default CacheEntryDispatcher;<|endoftext|>