<commit_before>/* eslint-env mocha */
/*global $, sinon, expect */
describe('jsonproxy', function() {
  var loc = window.location,
    origin = loc.origin || loc.protocol + '//' + loc.host,
    proxy = 'https://jsonp.afeld.me/',
    packagePath = loc.pathname.replace(/test.html/, 'package.json'),
    sandbox = sinon.createSandbox();

  afterEach(function() {
    sandbox.restore();
  });

  function sharedTests() {
    it('should do standard ajax for relative domains', function(done) {
      $.jsonp({
        url: packagePath,

        beforeSend: function(_, settings) {
          expect(settings.url).to.be(packagePath);
        }
      }).done(function(data) {
        expect(data.name).to.equal('jsonp');
        done();
      });
    });

    it('should do standard ajax for the same domain', function(done) {
      var url = origin + packagePath;

      $.jsonp({
        url: url,

        beforeSend: function(_, settings) {
          expect(settings.url).to.be(url);
        }
      }).done(function(data) {
        expect(data.name).to.equal('jsonp');
        done();
      });
    });

    it('should use the proxy for a mismatched protocol', function(done) {
      sandbox.stub($.jsonp, 'getLocation').callsFake(function() {
        return {
          hash: '',
          host: 'foo.com',
          hostname: 'foo.com',
          href: 'https://foo.com/other/path',
          origin: 'https://foo.com',
          pathname: '/other/path',
          port: '',
          protocol: 'https:'
        };
      });

      var url = 'http://foo.com/other/path';

      $.jsonp({
        url: url,

        beforeSend: function(_, settings) {
          expect(settings.url).to.contain(proxy + '?');
          expect(settings.url).to.contain('url=' + encodeURIComponent(url));

          done();
          return false;
        }
      });
    });
  }

  describe('with browser CORS support', function() {
    var origCors;
    before(function() {
      origCors = $.support.cors;
      $.support.cors = true;
    });

    after(function() {
      $.support.cors = origCors;
    });

    sharedTests();

    it('should use the CORS proxy', function(done) {
      var url = 'http://foo.com/bar';

      $.jsonp({
        url: url,

        beforeSend: function(_, settings) {
          expect(settings.url).to.be(proxy + '?url=' + encodeURIComponent(url));

          done();
          return false;
        }
      });
    });

    it('should use CORS proxy even if the URL supports JSONP', function(done) {
      var url = 'http://foo.com/bar';

      $.jsonp({
        url: url,
        jsonpSupport: true,

        beforeSend: function(_, settings) {
          expect(settings.url).to.be(proxy + '?url=' + encodeURIComponent(url));

          done();
          return false;
        }
      });
    });

    it('should handle requests for text to relative path', function(done) {
      var url = loc.pathname.replace(/test.html/, 'client/test/data.txt');

      $.jsonp({
        url: url,
        dataType: 'text',

        beforeSend: function(_, settings) {
          expect(settings.url).to.be(url);
        }
      }).done(function(text) {
        expect(text).to.equal('hello world');
        done();
      });
    });

    it('should handle requests for text across domains', function(done) {
      var url = 'http://foo.com/data.txt';

      $.jsonp({
        url: url,
        dataType: 'text',

        beforeSend: function(_, settings) {
          expect(settings.url).to.be(proxy + '?url=' + encodeURIComponent(url));

          done();
          return false;
        }
      });
    });

    it("should handle the 'data' option", function(done) {
      $.jsonp({
        url: 'http://foo.com',
        data: {
          bar: 'baz'
        },

        beforeSend: function(_, settings) {
          expect(settings.url).to.be(
            proxy + '?url=http%3A%2F%2Ffoo.com%2F%3Fbar%3Dbaz'
          );

          done();
          return false;
        }
      });
    });

    describe('when API has corsSupport', function() {
      it('should use the URL directly', function(done) {
        var url = 'http://foo.com/bar';

        $.jsonp({
          url: url,
          corsSupport: true,

          beforeSend: function(_, settings) {
            expect(settings.url).to.be(url);

            done();
            return false;
          }
        });
      });

      it('should favor CORS to JSONP on the URL directly', function(done) {
        var url = 'http://foo.com/bar';

        $.jsonp({
          url: url,
          corsSupport: true,
          jsonpSupport: true,

          beforeSend: function(_, settings) {
            expect(settings.url).to.be(url);

            done();
            return false;
          }
        });
      });

      it("should use the proxy if protocols don't match", function(done) {
        var url = 'http://foo.com/bar';

        sandbox.stub($.jsonp, 'getLocation').callsFake(function() {
          return {
            hash: '',
            host: 'foo.com',
            hostname: 'foo.com',
            href: 'https://foo.com/other/path',
            origin: 'https://foo.com',
            pathname: '/other/path',
            port: '',
            protocol: 'https:'
          };
        });

        $.jsonp({
          url: url,
          corsSupport: true,

          beforeSend: function(_, settings) {
            expect(settings.url).to.be(
              proxy + '?url=' + encodeURIComponent(url)
            );

            done();
            return false;
          }
        });
      });
    });
  });

  describe('without browser CORS support', function() {
    var origCors;
    before(function() {
      origCors = $.support.cors;
      $.support.cors = false;
    });

    after(function() {
      $.support.cors = origCors;
    });

    sharedTests();

    it('should use the JSONP proxy', function(done) {
      var url = 'http://foo.com/bar';

      $.jsonp({
        url: url,

        beforeSend: function(_, settings) {
          expect(settings.url).to.contain(proxy + '?');
          expect(settings.url).to.contain('url=' + encodeURIComponent(url));
          expect(settings.url).to.contain('callback=jQuery');
          // should set default timeout
          expect(settings.timeout).to.be.a('number');

          done();
          return false;
        }
      });
    });

    it('should handle requests for text across domains', function(done) {
      var url = 'http://foo.com/data.txt';

      $.jsonp({
        url: url,
        dataType: 'text',

        beforeSend: function(_, settings) {
          expect(settings.dataType).to.be('jsonp');

          done();
          return false;
        }
      });
    });

    describe('when API has jsonpSupport', function() {
      it('should use the URL directly', function(done) {
        var url = 'http://foo.com/bar';

        $.jsonp({
          url: url,
          jsonpSupport: true,

          beforeSend: function(_, settings) {
            expect(settings.url).to.contain(url + '?callback=jQuery');
            // should set default timeout
            expect(settings.timeout).to.be.a('number');

            done();
            return false;
          }
        });
      });
    });
  });
});
<commit_msg>format client test with prettier<commit_after>/* eslint-env mocha */
/*global $, sinon, expect */
describe('jsonproxy', function () {
  var loc = window.location,
    origin = loc.origin || loc.protocol + '//' + loc.host,
    proxy = 'https://jsonp.afeld.me/',
    packagePath = loc.pathname.replace(/test.html/, 'package.json'),
    sandbox = sinon.createSandbox();

  afterEach(function () {
    sandbox.restore();
  });

  function sharedTests() {
    it('should do standard ajax for relative domains', function (done) {
      $.jsonp({
        url: packagePath,

        beforeSend: function (_, settings) {
          expect(settings.url).to.be(packagePath);
        },
      }).done(function (data) {
        expect(data.name).to.equal('jsonp');
        done();
      });
    });

    it('should do standard ajax for the same domain', function (done) {
      var url = origin + packagePath;

      $.jsonp({
        url: url,

        beforeSend: function (_, settings) {
          expect(settings.url).to.be(url);
        },
      }).done(function (data) {
        expect(data.name).to.equal('jsonp');
        done();
      });
    });

    it('should use the proxy for a mismatched protocol', function (done) {
      sandbox.stub($.jsonp, 'getLocation').callsFake(function () {
        return {
          hash: '',
          host: 'foo.com',
          hostname: 'foo.com',
          href: 'https://foo.com/other/path',
          origin: 'https://foo.com',
          pathname: '/other/path',
          port: '',
          protocol: 'https:',
        };
      });

      var url = 'http://foo.com/other/path';

      $.jsonp({
        url: url,

        beforeSend: function (_, settings) {
          expect(settings.url).to.contain(proxy + '?');
          expect(settings.url).to.contain('url=' + encodeURIComponent(url));

          done();
          return false;
        },
      });
    });
  }

  describe('with browser CORS support', function () {
    var origCors;
    before(function () {
      origCors = $.support.cors;
      $.support.cors = true;
    });

    after(function () {
      $.support.cors = origCors;
    });

    sharedTests();

    it('should use the CORS proxy', function (done) {
      var url = 'http://foo.com/bar';

      $.jsonp({
        url: url,

        beforeSend: function (_, settings) {
          expect(settings.url).to.be(proxy + '?url=' + encodeURIComponent(url));

          done();
          return false;
        },
      });
    });

    it('should use CORS proxy even if the URL supports JSONP', function (done) {
      var url = 'http://foo.com/bar';

      $.jsonp({
        url: url,
        jsonpSupport: true,

        beforeSend: function (_, settings) {
          expect(settings.url).to.be(proxy + '?url=' + encodeURIComponent(url));

          done();
          return false;
        },
      });
    });

    it('should handle requests for text to relative path', function (done) {
      var url = loc.pathname.replace(/test.html/, 'client/test/data.txt');

      $.jsonp({
        url: url,
        dataType: 'text',

        beforeSend: function (_, settings) {
          expect(settings.url).to.be(url);
        },
      }).done(function (text) {
        expect(text).to.equal('hello world');
        done();
      });
    });

    it('should handle requests for text across domains', function (done) {
      var url = 'http://foo.com/data.txt';

      $.jsonp({
        url: url,
        dataType: 'text',

        beforeSend: function (_, settings) {
          expect(settings.url).to.be(proxy + '?url=' + encodeURIComponent(url));

          done();
          return false;
        },
      });
    });

    it("should handle the 'data' option", function (done) {
      $.jsonp({
        url: 'http://foo.com',
        data: {
          bar: 'baz',
        },

        beforeSend: function (_, settings) {
          expect(settings.url).to.be(
            proxy + '?url=http%3A%2F%2Ffoo.com%2F%3Fbar%3Dbaz'
          );

          done();
          return false;
        },
      });
    });

    describe('when API has corsSupport', function () {
      it('should use the URL directly', function (done) {
        var url = 'http://foo.com/bar';

        $.jsonp({
          url: url,
          corsSupport: true,

          beforeSend: function (_, settings) {
            expect(settings.url).to.be(url);

            done();
            return false;
          },
        });
      });

      it('should favor CORS to JSONP on the URL directly', function (done) {
        var url = 'http://foo.com/bar';

        $.jsonp({
          url: url,
          corsSupport: true,
          jsonpSupport: true,

          beforeSend: function (_, settings) {
            expect(settings.url).to.be(url);

            done();
            return false;
          },
        });
      });

      it("should use the proxy if protocols don't match", function (done) {
        var url = 'http://foo.com/bar';

        sandbox.stub($.jsonp, 'getLocation').callsFake(function () {
          return {
            hash: '',
            host: 'foo.com',
            hostname: 'foo.com',
            href: 'https://foo.com/other/path',
            origin: 'https://foo.com',
            pathname: '/other/path',
            port: '',
            protocol: 'https:',
          };
        });

        $.jsonp({
          url: url,
          corsSupport: true,

          beforeSend: function (_, settings) {
            expect(settings.url).to.be(
              proxy + '?url=' + encodeURIComponent(url)
            );

            done();
            return false;
          },
        });
      });
    });
  });

  describe('without browser CORS support', function () {
    var origCors;
    before(function () {
      origCors = $.support.cors;
      $.support.cors = false;
    });

    after(function () {
      $.support.cors = origCors;
    });

    sharedTests();

    it('should use the JSONP proxy', function (done) {
      var url = 'http://foo.com/bar';

      $.jsonp({
        url: url,

        beforeSend: function (_, settings) {
          expect(settings.url).to.contain(proxy + '?');
          expect(settings.url).to.contain('url=' + encodeURIComponent(url));
          expect(settings.url).to.contain('callback=jQuery');
          // should set default timeout
          expect(settings.timeout).to.be.a('number');

          done();
          return false;
        },
      });
    });

    it('should handle requests for text across domains', function (done) {
      var url = 'http://foo.com/data.txt';

      $.jsonp({
        url: url,
        dataType: 'text',

        beforeSend: function (_, settings) {
          expect(settings.dataType).to.be('jsonp');

          done();
          return false;
        },
      });
    });

    describe('when API has jsonpSupport', function () {
      it('should use the URL directly', function (done) {
        var url = 'http://foo.com/bar';

        $.jsonp({
          url: url,
          jsonpSupport: true,

          beforeSend: function (_, settings) {
            expect(settings.url).to.contain(url + '?callback=jQuery');
            // should set default timeout
            expect(settings.timeout).to.be.a('number');

            done();
            return false;
          },
        });
      });
    });
  });
});
<|endoftext|>


<commit_before>var InputHandler;

InputHandler = function() {
  this.keysDown = {};
  this.lastKey = null;
  this.mouse = {};
  this.mouse = {
    x: 0,
    y: 0,
    clicked: false,
    down: false
  };
};

/* Useful key abstractions courtesy:
http://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes
*/
InputHandler.prototype.KEY = {
  'BACK_SPACE': 8,
  'TAB': 9,
  'RTN': 13,
  'SHIFT': 16,
  'CTRL': 17,
  'ALT': 18,
  'BREAK': 19,
  'CAPS': 20,
  'ESC': 27,
  'PAGE_UP': 33,
  'PAGE_DOWN': 34,
  'END': 35,
  'HOME': 36,
  'LEFT': 37,
  'UP': 38,
  'RIGHT': 39,
  'DOWN': 40,
  'INSERT': 45,
  'DELETE': 46,
  'NUM_PAD_MULT': 106,
  'NUM_PAD_PLUS': 107,
  'NUM_PAD_MINUS': 109,
  'NUM_PAD_PERIOD': 110,
  'NUM_PAD_DIV': 111,
  'NUM_LOCK': 144,
  'SCROLL_LOCK': 145,
  'SEMI_COLON': 186,
  'EQUAL_SIGN': 187,
  'COMMA': 188,
  'DASH': 189,
  'PERIOD': 190,
  'FORWARD_SLASH': 191,
  'GRAVE_ACCENT': 192,
  'OPEN_BRACKET': 219,
  'BACK_SLASH': 220,
  'CLOSE_BRACKET': 221,
  'SINGLE_QUOTE': 222
};

(function addNumPairs() {
  var i;
  for (i = 0; i < 10; i++) {
    InputHandler.prototype.KEY['' + i] = 48 + i;
  }
})();

(function addLetterPairs() {
  var i;
  for (i = 65; i < 91; i++) {
    InputHandler.prototype.KEY['' + String.fromCharCode(i)] = i;
  }
})();

(function addNumPadPairs() {
  var i;
  for (i = 96; i < 106; i++) {
    InputHandler.prototype.KEY['NUM_PAD_' + (i - 96)] = i;
  }
})();

(function addFKeyPairs() {
  var i;
  for (i = 112; i < 124; i++) {
    InputHandler.prototype.KEY['F' + (i - 111)] = i;
  }
})();

InputHandler.prototype.register = function(canvas) {
  var self;
  self = this;
  canvas.addEventListener('keydown', function(e) {
    if (e.keyCode !== self.KEY.F5 || e.keyCode !== self.KEY.F11) {
      e.preventDefault();
      e.stopPropagation();
    }
    self.keysDown[e.keyCode] = true;
    self.lastKey = e.keyCode;
  });

  canvas.addEventListener('keyup', function(e) {
    if (e.keyCode !== self.KEY.F5 || e.keyCode !== self.KEY.F11) {
      e.preventDefault();
      e.stopPropagation();
    }
    delete self.keysDown[e.keyCode];
  });

  canvas.addEventListener('mouseover', function(e) {
    self.mouse.x = e.offsetX;
    self.mouse.y = e.offsetY;
  });

  canvas.addEventListener('mousedown', function(e) {
    self.mouse.down = true;
  });

  canvas.addEventListener('click', function(e) {
    self.mouse.clicked = true;
  });

  canvas.addEventListener('mouseup', function(e) {
    self.mouse.down = false;
  });
};
<commit_msg>Create update function to determine when a key is pressed,  that way even though the keydown listener fires multiple events I am able to treat them as 'one'<commit_after>var InputHandler;

InputHandler = function() {
  this.keysDown = {};
  this.lastKey = null;
  this.mouse = {};
  this.mouse = {
    x: 0,
    y: 0,
    clicked: false,
    down: false
  };
  this.pressed = false;
};

/* Useful key abstractions courtesy:
http://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes
*/
InputHandler.prototype.KEY = {
  'BACK_SPACE': 8,
  'TAB': 9,
  'RTN': 13,
  'SHIFT': 16,
  'CTRL': 17,
  'ALT': 18,
  'BREAK': 19,
  'CAPS': 20,
  'ESC': 27,
  'PAGE_UP': 33,
  'PAGE_DOWN': 34,
  'END': 35,
  'HOME': 36,
  'LEFT': 37,
  'UP': 38,
  'RIGHT': 39,
  'DOWN': 40,
  'INSERT': 45,
  'DELETE': 46,
  'NUM_PAD_MULT': 106,
  'NUM_PAD_PLUS': 107,
  'NUM_PAD_MINUS': 109,
  'NUM_PAD_PERIOD': 110,
  'NUM_PAD_DIV': 111,
  'NUM_LOCK': 144,
  'SCROLL_LOCK': 145,
  'SEMI_COLON': 186,
  'EQUAL_SIGN': 187,
  'COMMA': 188,
  'DASH': 189,
  'PERIOD': 190,
  'FORWARD_SLASH': 191,
  'GRAVE_ACCENT': 192,
  'OPEN_BRACKET': 219,
  'BACK_SLASH': 220,
  'CLOSE_BRACKET': 221,
  'SINGLE_QUOTE': 222
};

(function addNumPairs() {
  var i;
  for (i = 0; i < 10; i++) {
    InputHandler.prototype.KEY['' + i] = 48 + i;
  }
})();

(function addLetterPairs() {
  var i;
  for (i = 65; i < 91; i++) {
    InputHandler.prototype.KEY['' + String.fromCharCode(i)] = i;
  }
})();

(function addNumPadPairs() {
  var i;
  for (i = 96; i < 106; i++) {
    InputHandler.prototype.KEY['NUM_PAD_' + (i - 96)] = i;
  }
})();

(function addFKeyPairs() {
  var i;
  for (i = 112; i < 124; i++) {
    InputHandler.prototype.KEY['F' + (i - 111)] = i;
  }
})();

InputHandler.prototype.register = function(canvas) {
  var self;
  self = this;
  canvas.addEventListener('keydown', function(e) {
    if (e.keyCode !== self.KEY.F5 || e.keyCode !== self.KEY.F11) {
      e.preventDefault();
      e.stopPropagation();
    }
    self.keysDown[e.keyCode] = true;
  });

  canvas.addEventListener('keyup', function(e) {
    if (e.keyCode !== self.KEY.F5 || e.keyCode !== self.KEY.F11) {
      e.preventDefault();
      e.stopPropagation();
    }
    delete self.keysDown[e.keyCode];
  });

  canvas.addEventListener('mouseover', function(e) {
    self.mouse.x = e.offsetX;
    self.mouse.y = e.offsetY;
  });

  canvas.addEventListener('mousedown', function(e) {
    self.mouse.down = true;
  });

  canvas.addEventListener('click', function(e) {
    self.mouse.clicked = true;
  });

  canvas.addEventListener('mouseup', function(e) {
    self.mouse.down = false;
  });
};

InputHandler.prototype.update = function() {
  var keys, keyDown;
  keys = Object.keys(this.keysDown);
  keyDown = keys.length > 0 ? this.keysDown[keys[0]] : null;
  this.pressed =  keyDown !== this.lastKey;
  this.lastKey = this.keysDown[keys[0]];
};
<|endoftext|>


<commit_before>import {classes} from 'istanbul-lib-coverage';

function cloneLocation(loc) {
    return {
        start: {
            line: loc.start.line,
            column: loc.start.column
        },
        end: {
            line: loc.end.line,
            column: loc.end.column
        }
    };
}
/**
 * SourceCoverage provides mutation methods to manipulate the structure of
 * a file coverage object. Used by the instrumenter to create a full coverage
 * object for a file incrementally.
 *
 * @private
 * @param pathOrObj {String|Object} - see the argument for {@link FileCoverage}
 * @extends FileCoverage
 * @constructor
 */
class SourceCoverage extends classes.FileCoverage {
    constructor(pathOrObj) {
        super(pathOrObj);
        this.meta = {
            last: {
                s: 0,
                f: 0,
                b: 0
            }
        };
    }

    newStatement(loc) {
        this.meta.last.s += 1;
        var s = this.meta.last.s;
        this.data.statementMap[s] = cloneLocation(loc);
        this.data.s[s] = 0;
        return s;
    }

    newFunction(name, decl, loc) {
        this.meta.last.f += 1;
        var f = this.meta.last.f;
        name = name || '(anonymous_' + f + ')';
        this.data.fnMap[f] = {
            name: name,
            decl: cloneLocation(decl),
            loc: cloneLocation(loc)
        };
        this.data.f[f] = 0;
        return f;
    }

    newBranch(type, loc) {
        this.meta.last.b += 1;
        var b = this.meta.last.b;
        this.data.b[b] = [];
        this.data.branchMap[b] = {
            loc: cloneLocation(loc),
            type: type,
            locations: []
        };
        return b;
    }

    addBranchPath(name, location) {
        var bMeta = this.data.branchMap[name],
            counts = this.data.b[name];

        /* istanbul ignore if: paranoid check */
        if (!bMeta) {
            throw new Error("Invalid branch " + name);
        }
        bMeta.locations.push(cloneLocation(location));
        counts.push(0);
        return counts.length - 1;
    }

    freeze() {
        // prune empty branches
        var map = this.data.branchMap,
            branches = this.data.b;
        Object.keys(map).forEach(function (b) {
            if (map[b].locations.length === 0) {
                delete map[b];
                delete branches[b];
            }
        });
    }
}

export { SourceCoverage };

<commit_msg>fix: guard against invalid loc (#16)<commit_after>import {classes} from 'istanbul-lib-coverage';

function cloneLocation(loc) {
    return {
        start: {
            line: loc && loc.start.line,
            column: loc && loc.start.column
        },
        end: {
            line: loc && loc.end.line,
            column: loc && loc.end.column
        }
    };
}
/**
 * SourceCoverage provides mutation methods to manipulate the structure of
 * a file coverage object. Used by the instrumenter to create a full coverage
 * object for a file incrementally.
 *
 * @private
 * @param pathOrObj {String|Object} - see the argument for {@link FileCoverage}
 * @extends FileCoverage
 * @constructor
 */
class SourceCoverage extends classes.FileCoverage {
    constructor(pathOrObj) {
        super(pathOrObj);
        this.meta = {
            last: {
                s: 0,
                f: 0,
                b: 0
            }
        };
    }

    newStatement(loc) {
        this.meta.last.s += 1;
        var s = this.meta.last.s;
        this.data.statementMap[s] = cloneLocation(loc);
        this.data.s[s] = 0;
        return s;
    }

    newFunction(name, decl, loc) {
        this.meta.last.f += 1;
        var f = this.meta.last.f;
        name = name || '(anonymous_' + f + ')';
        this.data.fnMap[f] = {
            name: name,
            decl: cloneLocation(decl),
            loc: cloneLocation(loc)
        };
        this.data.f[f] = 0;
        return f;
    }

    newBranch(type, loc) {
        this.meta.last.b += 1;
        var b = this.meta.last.b;
        this.data.b[b] = [];
        this.data.branchMap[b] = {
            loc: cloneLocation(loc),
            type: type,
            locations: []
        };
        return b;
    }

    addBranchPath(name, location) {
        var bMeta = this.data.branchMap[name],
            counts = this.data.b[name];

        /* istanbul ignore if: paranoid check */
        if (!bMeta) {
            throw new Error("Invalid branch " + name);
        }
        bMeta.locations.push(cloneLocation(location));
        counts.push(0);
        return counts.length - 1;
    }

    freeze() {
        // prune empty branches
        var map = this.data.branchMap,
            branches = this.data.b;
        Object.keys(map).forEach(function (b) {
            if (map[b].locations.length === 0) {
                delete map[b];
                delete branches[b];
            }
        });
    }
}

export { SourceCoverage };
<|endoftext|>


<commit_before>Template.nav.helpers({
  site_title: function(){
    return getSetting('title');
  },
  logo_url: function(){
    return getSetting('logoUrl');
  },
  logo_height: function(){
    return getSetting('logoHeight');
  },
  logo_width: function(){
    return getSetting('logoWidth');
  },
  logo_top: function(){
    return Math.floor((70-getSetting('logoHeight'))/2);
  },
  logo_offset: function(){
    return -Math.floor(getSetting('logoWidth')/2);
  },
  intercom: function(){
    return !!getSetting('intercomId');
  },
  canPost: function(){
    return canPost(Meteor.user());
  },
  requirePostsApproval: function(){
    return getSetting('requirePostsApproval');
  },
  hasCategories: function(){
    return Categories.find().count();
  },
  categories: function(){
    return Categories.find();
  },
  categoryLink: function () {
    return getCategoryUrl(this.slug);
  },
  searchQuery: function () {
    return Session.get("searchQuery");
  },
  searchQueryEmpty: function () {
    return !!Session.get("searchQuery") ? '' : 'empty';
  }
});

Template.nav.preserve({
  'input#search': function (node) { return node.id; }
});

Template.nav.rendered=function(){

  if(!Meteor.user()){
    $('.login-link-text').text("Sign Up/Sign In");
  }else{
    $('#login-buttons-logout').before('View Profile');
    $('#login-buttons-logout').before('Edit Account');
  }
};

Template.nav.events = {
  'click #logout': function(e){
    e.preventDefault();
    Meteor.logout();
  },
  'click #mobile-menu': function(e){
    e.preventDefault();
    $('body').toggleClass('mobile-nav-open');
  },
  'click .login-header': function(e){
    e.preventDefault();
    Router.go('/account');
  },
  'keyup, change, search .search-field': function(e){
    e.preventDefault();
    var val = $(e.target).val(),
        $search = $('.search');
    if(val==''){
      // if search field is empty, just do nothing and show an empty template
      $search.addClass('empty');
      Session.set('searchQuery', '');
    }else{
      // if search field is not empty, add a delay to avoid firing new searches for every keystroke
      delay(function(){
        Session.set('searchQuery', val);
        $search.removeClass('empty');
        // if we're not already on the search page, go to it
        if(getCurrentRoute().indexOf('search') == -1)
          Router.go('/search');
      }, 500 );
    }
  }
};

// see: http://stackoverflow.com/questions/1909441/jquery-keyup-delay
var delay = (function(){
  var timer = 0;
  return function(callback, ms){
    clearTimeout (timer);
    timer = setTimeout(callback, ms);
  };
})();<commit_msg>remove change event for search<commit_after>Template.nav.helpers({
  site_title: function(){
    return getSetting('title');
  },
  logo_url: function(){
    return getSetting('logoUrl');
  },
  logo_height: function(){
    return getSetting('logoHeight');
  },
  logo_width: function(){
    return getSetting('logoWidth');
  },
  logo_top: function(){
    return Math.floor((70-getSetting('logoHeight'))/2);
  },
  logo_offset: function(){
    return -Math.floor(getSetting('logoWidth')/2);
  },
  intercom: function(){
    return !!getSetting('intercomId');
  },
  canPost: function(){
    return canPost(Meteor.user());
  },
  requirePostsApproval: function(){
    return getSetting('requirePostsApproval');
  },
  hasCategories: function(){
    return Categories.find().count();
  },
  categories: function(){
    return Categories.find();
  },
  categoryLink: function () {
    return getCategoryUrl(this.slug);
  },
  searchQuery: function () {
    return Session.get("searchQuery");
  },
  searchQueryEmpty: function () {
    return !!Session.get("searchQuery") ? '' : 'empty';
  }
});

Template.nav.preserve({
  'input#search': function (node) { return node.id; }
});

Template.nav.rendered=function(){

  if(!Meteor.user()){
    $('.login-link-text').text("Sign Up/Sign In");
  }else{
    $('#login-buttons-logout').before('View Profile');
    $('#login-buttons-logout').before('Edit Account');
  }
};

Template.nav.events = {
  'click #logout': function(e){
    e.preventDefault();
    Meteor.logout();
  },
  'click #mobile-menu': function(e){
    e.preventDefault();
    $('body').toggleClass('mobile-nav-open');
  },
  'click .login-header': function(e){
    e.preventDefault();
    Router.go('/account');
  },
  'keyup, search .search-field': function(e){
    e.preventDefault();
    var val = $(e.target).val(),
        $search = $('.search');
    if(val==''){
      // if search field is empty, just do nothing and show an empty template
      $search.addClass('empty');
      Session.set('searchQuery', '');
    }else{
      // if search field is not empty, add a delay to avoid firing new searches for every keystroke
      delay(function(){
        Session.set('searchQuery', val);
        $search.removeClass('empty');
        // if we're not already on the search page, go to it
        if(getCurrentRoute().indexOf('search') == -1)
          Router.go('/search');
      }, 500 );
    }
  }
};

// see: http://stackoverflow.com/questions/1909441/jquery-keyup-delay
var delay = (function(){
  var timer = 0;
  return function(callback, ms){
    clearTimeout (timer);
    timer = setTimeout(callback, ms);
  };
})();<|endoftext|>


<commit_before>window.SQLjs_driver = function () {

	var db = null;

	var splitStatement = function (statements)
	{
		return statements.split(/;\s*\r?\n|$/);
	}

	this.buildSchema = function (args) {

		try {

			/*
			 * Closure used to handle both cases of when the sql.js library
			 * has already been loaded, or when it has not yet been.
			 */
			var jsBuildSchema = function () {

				db = SQL.open();
				$.each(splitStatement(args["ddl"]), function (i, statement) {
					db.exec(statement);
				});

				args["success"]();
			}

			//  If the sql.js code isn't yet loaded, do it now.
			if (window.SQL === undefined)
			{
				$.getScript("javascripts/sql.js", function (script, textStatus, jqXHR) {
					jsBuildSchema();
				}).fail(function(jqxhr, settings, exception){
					args["error"]("Your browser does not work with SQL.js.  Try using a different browser (Chrome, Firefox, IE 10, etc...), or a newer version of your current one.");
				});
			}
			else
			{
				if (db)
				{
					db.close();
				}

				jsBuildSchema();
			}

		}
		catch (e)
		{
			args["error"](e);
		}

	}

	this.executeQuery = function (args) {

		try {
			if (! db)
			{
				throw ("Database Schema not available!");
			}

			var returnSets = [];

			db.exec("BEGIN TRANSACTION");

			$.each(splitStatement(args["sql"]), function (i, statement) {
				var startTime = new Date();

				var setArray = [];

				try {
					setArray = db.exec(statement);

					exectionPlanArray = db.exec("EXPLAIN QUERY PLAN " + statement);

					var thisSet = {
						"SUCCEEDED": true,
						"EXECUTIONTIME": (new Date()) - startTime,
						"RESULTS": {
							"COLUMNS": [],
							"DATA": []
						},
						"EXECUTIONPLAN": {
							"COLUMNS": [],
							"DATA": []
						}

					};

					if (setArray.length)
					{
						$.each(setArray, function (rowNumber, row) {
							var rowVals = [];
							$.each(row, function (columnNumber, col) {
								if (rowNumber == 0)
								{
									thisSet["RESULTS"]["COLUMNS"].push(col.column);
								}
								rowVals.push(col.value);
							});
							thisSet["RESULTS"]["DATA"].push(rowVals);
						});
					}

					if (exectionPlanArray.length)
					{
						$.each(exectionPlanArray, function (rowNumber, row) {
							var rowVals = [];
							$.each(row, function (columnNumber, col) {
								if (rowNumber == 0)
								{
									thisSet["EXECUTIONPLAN"]["COLUMNS"].push(col.column);
								}
								rowVals.push(col.value);
							});
							thisSet["EXECUTIONPLAN"]["DATA"].push(rowVals);
						});
					}

					returnSets.push(thisSet);

				}
				catch (e)
				{
					var thisSet = {
						"SUCCEEDED": false,
						"EXECUTIONTIME": (new Date()) - startTime,
						"ERRORMESSAGE": e
					};
					returnSets.push(thisSet);
					return false; // breaks the each loop
				}

			});

			db.exec("ROLLBACK TRANSACTION");

			args["success"](returnSets);

		}
		catch (e)
		{
			args["error"](e);
		}

	}

	return this;

}
<commit_msg>Adding logic to prevent some types of queries from throwing errors when they have their execution plan explained, which would then trip up the whole process.<commit_after>window.SQLjs_driver = function () {

	var db = null;

	var splitStatement = function (statements)
	{
		return statements.split(/;\s*\r?\n|$/);
	}

	this.buildSchema = function (args) {

		try {

			/*
			 * Closure used to handle both cases of when the sql.js library
			 * has already been loaded, or when it has not yet been.
			 */
			var jsBuildSchema = function () {

				db = SQL.open();
				$.each(splitStatement(args["ddl"]), function (i, statement) {
					db.exec(statement);
				});

				args["success"]();
			}

			//  If the sql.js code isn't yet loaded, do it now.
			if (window.SQL === undefined)
			{
				$.getScript("javascripts/sql.js", function (script, textStatus, jqXHR) {
					jsBuildSchema();
				}).fail(function(jqxhr, settings, exception){
					args["error"]("Your browser does not work with SQL.js.  Try using a different browser (Chrome, Firefox, IE 10, etc...), or a newer version of your current one.");
				});
			}
			else
			{
				if (db)
				{
					db.close();
				}

				jsBuildSchema();
			}

		}
		catch (e)
		{
			args["error"](e);
		}

	}

	this.executeQuery = function (args) {

		try {
			if (! db)
			{
				throw ("Database Schema not available!");
			}

			var returnSets = [];

			db.exec("BEGIN TRANSACTION");

			$.each(splitStatement(args["sql"]), function (i, statement) {
				var startTime = new Date();

				var setArray = [];

				try {
					setArray = db.exec(statement);

					var thisSet = {
						"SUCCEEDED": true,
						"EXECUTIONTIME": (new Date()) - startTime,
						"RESULTS": {
							"COLUMNS": [],
							"DATA": []
						},
						"EXECUTIONPLAN": {
							"COLUMNS": [],
							"DATA": []
						}

					};

					if (setArray.length)
					{
						$.each(setArray, function (rowNumber, row) {
							var rowVals = [];
							$.each(row, function (columnNumber, col) {
								if (rowNumber == 0)
								{
									thisSet["RESULTS"]["COLUMNS"].push(col.column);
								}
								rowVals.push(col.value);
							});
							thisSet["RESULTS"]["DATA"].push(rowVals);
						});
					}

					try {

						exectionPlanArray = db.exec("EXPLAIN QUERY PLAN " + statement);

						if (exectionPlanArray.length)
						{
							$.each(exectionPlanArray, function (rowNumber, row) {
								var rowVals = [];
								$.each(row, function (columnNumber, col) {
									if (rowNumber == 0)
									{
										thisSet["EXECUTIONPLAN"]["COLUMNS"].push(col.column);
									}
									rowVals.push(col.value);
								});
								thisSet["EXECUTIONPLAN"]["DATA"].push(rowVals);
							});
						}

					}
					catch (e)
					{
						// if we get an error with the execution plan, just ignore and move on.
					}

					returnSets.push(thisSet);

				}
				catch (e)
				{
					var thisSet = {
						"SUCCEEDED": false,
						"EXECUTIONTIME": (new Date()) - startTime,
						"ERRORMESSAGE": e
					};
					returnSets.push(thisSet);
					return false; // breaks the each loop
				}

			});

			db.exec("ROLLBACK TRANSACTION");

			args["success"](returnSets);

		}
		catch (e)
		{
			args["error"](e);
		}

	}

	return this;

}
<|endoftext|>


<commit_before>/*jslint node: true */
'use strict';
var _ = require("lodash");
var util = require('util');

var GraphWrapper = require('./objects/graph');
var PipelineWrapper = require('./objects/pipeline');
var Argument = require('./arguments/argument');
var GremlinFunction = require('./functions/function');

module.exports = (function() {
  function GremlinScript() {
    this.script = '';
    this.params = {};
    this.paramCount = 0;
    this.identifierCount = 0;
  }

  /**
   * Append an arbitrary string to the script as a new line.
   *
   * @private
   * @param {String} line
   */
  GremlinScript.prototype.line = function(line) {
    this.script += line + '\n';
  };

  /**
   * Add bound parameters to the script. This currently only works when
   * using the formatted string. It does not work with gRex helpers/wrappers.
   *
   * @private
   * @param {Array} boundParams
   */
  GremlinScript.prototype.addBoundParams = function(boundParams) {
    var currentParamNames = [];
    var identifier;

    _.each(boundParams, function(boundParam) {
      identifier = 'p'+ this.paramCount++;
      this.params[identifier] = boundParam;
      currentParamNames.push(identifier);
    }, this);

    return currentParamNames;
  };

  /**
   * Handle a string statement using Node util.format() function.
   *
   * @private
   * @param {String} statement
   */
  GremlinScript.prototype.handleString = function(statement) {
    var currentParams = [statement];
    currentParams = currentParams.concat(this.addBoundParams(_.rest(arguments)));

    this.line(util.format.apply(util.format, currentParams));
  };

  /**
   * Handle a helper statement wrapped in one of gRex Wrapper classes
   *
   * @private
   * @param {ObjectWrapper} wrapper
   * @return {ObjectWrapper}
   */
  GremlinScript.prototype.handleHelper = function(wrapper) {
    this.line(wrapper.toGroovy());

    return wrapper;
  };

  /**
   * Identify a statement within the script with the provided optional
   * identifier. Will assign an automatica identifier instead.
   *
   * @public
   * @param {ObjectWrapper} wrapper
   * @param {String} identifier - an optional identifier
   */
  GremlinScript.prototype.var = function(wrapper, identifier) {
    identifier = identifier || 'i'+ this.identifierCount++;
    wrapper.identifier = identifier;
    var prefix = identifier + '=';

    var groovyCode = wrapper.toGroovy ? wrapper.toGroovy() : wrapper;

    this.script += prefix + groovyCode + '\n';

    return wrapper;
  };

  GremlinScript.prototype.appendStatement = function(statement) {
    if (arguments.length > 1) {
      // Assume query('g(%s)', 1) signature
      this.handleString.apply(this, arguments);
    } else if (_.isString(statement)) {
      // Assume query('g.v(1)') signature
      this.line(statement);
    } else if (statement) {
      // Assume query(g.v(1)) signature
      this.handleHelper(statement);
    }

    return this;
  };

  /**
   * Returns a function responsible for handling statements and ultimately
   * appending bits of Gremlin-Groovy to this GremlinScript.
   *
   * @private
   * @return {Function}
   */
  GremlinScript.prototype.getAppender = function() {
    var self = this;

    function GremlinAppender() {
      self.appendStatement.apply(self, arguments);
    }

    /**
     * Proxy some GremlinScript methods/getters to the appender
     */
    GremlinAppender.var = GremlinScript.prototype.var.bind(this);

    Object.defineProperty(GremlinAppender, 'script', {
      get: function() {
        return self.script;
      }
    });

    Object.defineProperty(GremlinAppender, 'params', {
      get: function() {
        return self.params;
      }
    });

    return GremlinAppender;
  };

  return GremlinScript;
})();
<commit_msg>GremlinAppender now returns GremlinScript instance<commit_after>/*jslint node: true */
'use strict';
var _ = require("lodash");
var util = require('util');

var GraphWrapper = require('./objects/graph');
var PipelineWrapper = require('./objects/pipeline');
var Argument = require('./arguments/argument');
var GremlinFunction = require('./functions/function');

module.exports = (function() {
  function GremlinScript() {
    this.script = '';
    this.params = {};
    this.paramCount = 0;
    this.identifierCount = 0;
  }

  /**
   * Append an arbitrary string to the script as a new line.
   *
   * @private
   * @param {String} line
   */
  GremlinScript.prototype.line = function(line) {
    this.script += line + '\n';
  };

  /**
   * Add bound parameters to the script. This currently only works when
   * using the formatted string. It does not work with gRex helpers/wrappers.
   *
   * @private
   * @param {Array} boundParams
   */
  GremlinScript.prototype.addBoundParams = function(boundParams) {
    var currentParamNames = [];
    var identifier;

    _.each(boundParams, function(boundParam) {
      identifier = 'p'+ this.paramCount++;
      this.params[identifier] = boundParam;
      currentParamNames.push(identifier);
    }, this);

    return currentParamNames;
  };

  /**
   * Handle a string statement using Node util.format() function.
   *
   * @private
   * @param {String} statement
   */
  GremlinScript.prototype.handleString = function(statement) {
    var currentParams = [statement];
    currentParams = currentParams.concat(this.addBoundParams(_.rest(arguments)));

    this.line(util.format.apply(util.format, currentParams));
  };

  /**
   * Handle a helper statement wrapped in one of gRex Wrapper classes
   *
   * @private
   * @param {ObjectWrapper} wrapper
   * @return {ObjectWrapper}
   */
  GremlinScript.prototype.handleHelper = function(wrapper) {
    this.line(wrapper.toGroovy());

    return wrapper;
  };

  /**
   * Identify a statement within the script with the provided optional
   * identifier. Will assign an automatica identifier instead.
   *
   * @public
   * @param {ObjectWrapper} wrapper
   * @param {String} identifier - an optional identifier
   */
  GremlinScript.prototype.var = function(wrapper, identifier) {
    identifier = identifier || 'i'+ this.identifierCount++;
    wrapper.identifier = identifier;
    var prefix = identifier + '=';

    var groovyCode = wrapper.toGroovy ? wrapper.toGroovy() : wrapper;

    this.script += prefix + groovyCode + '\n';

    return wrapper;
  };

  GremlinScript.prototype.appendStatement = function(statement) {
    if (arguments.length > 1) {
      // Assume query('g(%s)', 1) signature
      this.handleString.apply(this, arguments);
    } else if (_.isString(statement)) {
      // Assume query('g.v(1)') signature
      this.line(statement);
    } else if (statement) {
      // Assume query(g.v(1)) signature
      this.handleHelper(statement);
    }
  };

  /**
   * Returns a function responsible for handling statements and ultimately
   * appending bits of Gremlin-Groovy to this GremlinScript.
   *
   * @private
   * @return {Function}
   */
  GremlinScript.prototype.getAppender = function() {
    var self = this;

    function GremlinAppender() {
      self.appendStatement.apply(self, arguments);

      return self;
    }

    /**
     * Proxy some GremlinScript methods/getters to the appender
     */
    GremlinAppender.var = GremlinScript.prototype.var.bind(this);

    Object.defineProperty(GremlinAppender, 'script', {
      get: function() {
        return self.script;
      }
    });

    Object.defineProperty(GremlinAppender, 'params', {
      get: function() {
        return self.params;
      }
    });

    return GremlinAppender;
  };

  return GremlinScript;
})();
<|endoftext|>


<commit_before>RouterClass = function(routes) {
  this.url = window.location.toString().split('/').slice(0, 3).join('/');

  var OrderedHash = function() {
    this.keys = [];
    this.values = [];

    this.set = function(key, value) {
      var index = this.keys.indexOf(key);
      if(index >= 0) {
        this.values[index] = value;
      } else {
        this.keys.push(key);
        this.values.push(value);
      }
    };

    this.get = function(key) {
      var index = this.keys.indexOf(key);
      if(index >= 0) {
        return this.values[index];
      } else {
        return null;
      }
    };

    this.each = function(callback) {
      for(var i = 0; i < this.keys.length; i++) {
        callback(this.keys[i], this.values[i]);
      }
    };
  };

  this.resolveArguments = function(def, allArgs) {
    var i;
    var args = allArgs;
    var options = {};
    var urlOptions = new OrderedHash();

    // extract options
    if (allArgs.length > 0) {
      var last = allArgs[allArgs.length-1];
      if (typeof(last) === "object") {
        options = last;
        args = [];
        for (i = 0; i < allArgs.length - 1; i++) {
          args.push(allArgs[i]);
        }
      }
    }

    for (i = 0; i < args.length; i++) {
      var key = def.keys[i];
      var value = args[i];
      urlOptions.set(key, value);
    }

    for (var k in options) {
      if (true) { // mute JSlint
        urlOptions.set(k, options[k]);
      }
    }

    return urlOptions;
  };

  this.cleanupPath = function(path) {
    return path.replace(/\(.*(\:[a-z0-9_]+).*\)/g, "").replace(/[\(\)]/g, "");
  };

  this.bindRoute = function(key, def) {
    this[key] = function() {
      var args = this.resolveArguments(def, arguments);
      var path = def.path;
      var data = [];

      args.each(function(k,v) {
        if(path.search(k) >= 0) {
          path = path.replace(new RegExp(':'+k), encodeURIComponent(v));
        } else {
          data.push(k + '=' + encodeURIComponent(v));
        }
      });

      var route = this.cleanupPath(path);

      if (data.length > 0) {
        data = data.join('&');
        if (route.indexOf('?') > 0) {
          route += data;
        } else {
          route += '?' + data;
        }
      }
      return route;
    };

    this[key + '_path'] = function() {
      return this[key].apply(this, arguments);
    };

    this[key + '_url'] = function() {
      var route = this[key + '_path'].apply(this, arguments);
      return this.url + route;
    };
  };

  for (var key in routes) {
    if (true) { // mute JSlint
      this.bindRoute(key, routes[key]);
    }
  }

  this.named_routes = routes;
};

%global% = new RouterClass(%routes%);
<commit_msg>IE doesn't support Array.indexOf, so we just add it if not defined<commit_after>if(!Array.indexOf){
    Array.prototype.indexOf = function(obj){
        for(var i=0; i<this.length; i++){
            if(this[i] == obj){
                return i;
            }
        }
        return -1;
    }
}

RouterClass = function(routes) {
  this.url = window.location.toString().split('/').slice(0, 3).join('/');

  var OrderedHash = function() {
    this.keys = [];
    this.values = [];

    this.set = function(key, value) {
      var index = this.keys.indexOf(key);
      if(index >= 0) {
        this.values[index] = value;
      } else {
        this.keys.push(key);
        this.values.push(value);
      }
    };

    this.get = function(key) {
      var index = this.keys.indexOf(key);
      if(index >= 0) {
        return this.values[index];
      } else {
        return null;
      }
    };

    this.each = function(callback) {
      for(var i = 0; i < this.keys.length; i++) {
        callback(this.keys[i], this.values[i]);
      }
    };
  };

  this.resolveArguments = function(def, allArgs) {
    var i;
    var args = allArgs;
    var options = {};
    var urlOptions = new OrderedHash();

    // extract options
    if (allArgs.length > 0) {
      var last = allArgs[allArgs.length-1];
      if (typeof(last) === "object") {
        options = last;
        args = [];
        for (i = 0; i < allArgs.length - 1; i++) {
          args.push(allArgs[i]);
        }
      }
    }

    for (i = 0; i < args.length; i++) {
      var key = def.keys[i];
      var value = args[i];
      urlOptions.set(key, value);
    }

    for (var k in options) {
      if (true) { // mute JSlint
        urlOptions.set(k, options[k]);
      }
    }

    return urlOptions;
  };

  this.cleanupPath = function(path) {
    return path.replace(/\(.*(\:[a-z0-9_]+).*\)/g, "").replace(/[\(\)]/g, "");
  };

  this.bindRoute = function(key, def) {
    this[key] = function() {
      var args = this.resolveArguments(def, arguments);
      var path = def.path;
      var data = [];

      args.each(function(k,v) {
        if(path.search(k) >= 0) {
          path = path.replace(new RegExp(':'+k), encodeURIComponent(v));
        } else {
          data.push(k + '=' + encodeURIComponent(v));
        }
      });

      var route = this.cleanupPath(path);

      if (data.length > 0) {
        data = data.join('&');
        if (route.indexOf('?') > 0) {
          route += data;
        } else {
          route += '?' + data;
        }
      }
      return route;
    };

    this[key + '_path'] = function() {
      return this[key].apply(this, arguments);
    };

    this[key + '_url'] = function() {
      var route = this[key + '_path'].apply(this, arguments);
      return this.url + route;
    };
  };

  for (var key in routes) {
    if (true) { // mute JSlint
      this.bindRoute(key, routes[key]);
    }
  }

  this.named_routes = routes;
};

%global% = new RouterClass(%routes%);
<|endoftext|>


<commit_before>var fs = require("fs");

//------------------------------------------------------------------------------
// String Polyfill
//------------------------------------------------------------------------------

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
if (!String.prototype.startsWith) {
  String.prototype.startsWith = function(searchString, position) {
    position = position || 0;
    return this.indexOf(searchString, position) === position;
  };
}

//------------------------------------------------------------------------------
// Result Data
//------------------------------------------------------------------------------

function getInitialResult() {
  var result = {
    cases: [],
    currLabel: null, // "in" or "out"
    currCase: getInitialCase()
  };
  Object.preventExtensions(result);
  return result;
}

function getInitialCase() {
  var testCase = {
    "in": null,
    "out": null
  };
  Object.preventExtensions(testCase);
  return testCase;
}

//------------------------------------------------------------------------------
// Error Handling
//------------------------------------------------------------------------------

function error(fileLineNo, msg) {
  console.error("error at test-case line #" + (fileLineNo+1) + ": " + msg);
  process.exit(1);
}

//------------------------------------------------------------------------------
// Test case parsing
//------------------------------------------------------------------------------

function parseLine_endBlock(result, fileLineNo, line) {
  if (result.currLabel === null) {
    error(fileLineNo, "opening block must have a name: 'in' or 'out'.");
  }

  var isTestCaseDone = (result.currCase.out !== null);

  if (isTestCaseDone) {
    result.cases.push(result.currCase);
    result.currLabel = null;
    result.currCase = getInitialCase();
  }
  else {
    result.currLabel = null;
  }
}

function parseLine_startBlock(result, fileLineNo, line) {
  if (result.currLabel !== null) {
    error(fileLineNo, "must close previous block '" + result.currLabel + "' before starting new one.");
  }

  var label = line.substring("```".length);

  if (label !== "in" && label !== "out") {
    error(fileLineNo, "block name '" + label + "' must be either 'in' or 'out'.");
  }
  if (label === "in" && result.currCase.in !== null) {
    error(fileLineNo, "there is already an 'in' block for this test case.");
  }
  if (label === "out" && result.currCase.in === null) {
    error(fileLineNo, "must include an 'in' block before an 'out' block.");
  }

  result.currLabel = label;
  result.currCase[label] = {
    "fileLineNo": fileLineNo,
    "lines": [],
    "cursor": null
  };
}

function handleCursorDxLine(result, block, fileLineNo, cursorDx, x) {
  if (result.currLabel === "out") {
    error(fileLineNo, "no ^ cursorDx annotation allowed in output block.");
  }
  if (block.cursor === null || block.cursor.cursorX !== x) {
    error(fileLineNo, "the ^ cursorDx annotation does not point to a cursor.");
  }

  block.cursor.cursorDx = cursorDx;
  return true;
}

function handleErrorLine(result, block, fileLineNo, name, x) {
  if (result.currLabel === "in") {
    error(fileLineNo, "no ^ error annotation allowed in input block.");
  }
  if (block.error) {
    error(fileLineNo, "only one ^ error annotation allowed in output block.");
  }

  var lineNo = block.lines.length-1;

  // shift x position back if previous line has cursor before our error caret
  if (block.cursor &&
      block.cursor.cursorLine === lineNo &&
      block.cursor.cursorX < x) {
    x--;
  }

  block.error = {
    "name": name,
    "lineNo": lineNo,
    "x": x,
  };
  return true;
}

function handleCursorPreviewScope(result, block, fileLineNo, x) {
  if (result.currLabel === "out") {
    error(fileLineNo, "no ^ previewCursorScope annotation allowed in output block.");
  }
  if (block.cursor === null || block.cursor.cursorX !== x) {
    error(fileLineNo, "the ^ previewCursorScope annotation does not point to a cursor.");
  }

  block.cursor.previewCursorScope = true;
  return true;
}

function handleCaretLine(result, block, fileLineNo, line) {
  var errorMatch = /^\s*\^\s*([a-z-]+)$/.exec(line);
  var cursorDxMatch = /^\s*\^\s*cursorDx (-?\d+)\s*$/.exec(line);
  var cursorPreviewScopeMatch = /^\s*\^\spreviewCursorScope\s*$/.exec(line);
  var x = line.indexOf("^");

  if (errorMatch) {
    var name = errorMatch[1];
    return handleErrorLine(result, block, fileLineNo, name, x);
  }
  else if (cursorDxMatch) {
    var cursorDx = parseInt(cursorDxMatch[1]);
    return handleCursorDxLine(result, block, fileLineNo, cursorDx, x);
  }
  else if (cursorPreviewScopeMatch) {
    return handleCursorPreviewScope(result, block, fileLineNo, x);
  }

  return false;
}

function handleCursor(block, fileLineNo, line) {
  var cursorX = line.indexOf("|");
  if (cursorX !== -1) {
    if (block.cursor !== null) {
      error(fileLineNo, "only one cursor allowed.  cursor already found at line", block.cursor.cursorLine);
    }

    var lineClean = line.split("|").join("");
    if (lineClean.length < line.length-1) {
      error(fileLineNo, "only one cursor allowed");
    }
    line = lineClean;

    block.cursor = {
      "cursorX": cursorX,
      "cursorLine": block.lines.length
    };
  }
  return line;
}

function parseLine_insideBlock(result, fileLineNo, line) {
  var block = result.currCase[result.currLabel];

  if (handleCaretLine(result, block, fileLineNo, line)) {
    return;
  }

  line = handleCursor(block, fileLineNo, line);
  block.lines.push(line);
}

function parseLine_default(result, fileLineNo, line) {
  return result;
}

function parseLine(result, fileLineNo, line) {

  var f;
  if (line === "```")                 { f = parseLine_endBlock; }
  else if (line.startsWith("```"))    { f = parseLine_startBlock; }
  else if (result.currLabel !== null) { f = parseLine_insideBlock; }
  else                                { f = parseLine_default; }

  return f(result, fileLineNo, line);
}

function parseText(text) {
  var lines = text.split("\n");
  var result = getInitialResult();
  var i;
  for (i=0; i<lines.length; i++) {
    parseLine(result, i, lines[i]);
  }

  if (result.currLabel !== null) {
    error("EOF", "code block not closed");
  }
  if (result.currCase.in !== null ||
      result.currCase.out !== null) {
    error("EOF", "test case 'out' block not completed");
  }

  return result.cases;
}

//------------------------------------------------------------------------------
// JSON builder
//------------------------------------------------------------------------------

var casesPath = __dirname;

function buildJson(name) {
  // JSON.stringify(data, null, "  ");
  var inFile = casesPath + "/" + name + ".md";
  var outFile = casesPath + "/" + name + ".json";
  var inText = fs.readFileSync(inFile, "utf8");

  console.log(
    "  compiling " +
    name + ".md" +
    "  -->  " +
    name + ".json"
  );

  var cases = parseText(inText);
  var outText = JSON.stringify(cases, null, "  ");
  fs.writeFileSync(outFile, outText);
}

function buildAll() {
  console.log("\nReading test cases described in Markdown and compiling to JSON...");
  buildJson("indent-mode");
  buildJson("paren-mode");
  console.log();
}

//------------------------------------------------------------------------------
// Exports and Entry
//------------------------------------------------------------------------------

// export api
exports.buildAll = buildAll;

// allow running this file directly with Node
if (require.main === module) {
  buildAll();
}
<commit_msg>test builder: check error annotation syntax last since it is the least specific<commit_after>var fs = require("fs");

//------------------------------------------------------------------------------
// String Polyfill
//------------------------------------------------------------------------------

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
if (!String.prototype.startsWith) {
  String.prototype.startsWith = function(searchString, position) {
    position = position || 0;
    return this.indexOf(searchString, position) === position;
  };
}

//------------------------------------------------------------------------------
// Result Data
//------------------------------------------------------------------------------

function getInitialResult() {
  var result = {
    cases: [],
    currLabel: null, // "in" or "out"
    currCase: getInitialCase()
  };
  Object.preventExtensions(result);
  return result;
}

function getInitialCase() {
  var testCase = {
    "in": null,
    "out": null
  };
  Object.preventExtensions(testCase);
  return testCase;
}

//------------------------------------------------------------------------------
// Error Handling
//------------------------------------------------------------------------------

function error(fileLineNo, msg) {
  console.error("error at test-case line #" + (fileLineNo+1) + ": " + msg);
  process.exit(1);
}

//------------------------------------------------------------------------------
// Test case parsing
//------------------------------------------------------------------------------

function parseLine_endBlock(result, fileLineNo, line) {
  if (result.currLabel === null) {
    error(fileLineNo, "opening block must have a name: 'in' or 'out'.");
  }

  var isTestCaseDone = (result.currCase.out !== null);

  if (isTestCaseDone) {
    result.cases.push(result.currCase);
    result.currLabel = null;
    result.currCase = getInitialCase();
  }
  else {
    result.currLabel = null;
  }
}

function parseLine_startBlock(result, fileLineNo, line) {
  if (result.currLabel !== null) {
    error(fileLineNo, "must close previous block '" + result.currLabel + "' before starting new one.");
  }

  var label = line.substring("```".length);

  if (label !== "in" && label !== "out") {
    error(fileLineNo, "block name '" + label + "' must be either 'in' or 'out'.");
  }
  if (label === "in" && result.currCase.in !== null) {
    error(fileLineNo, "there is already an 'in' block for this test case.");
  }
  if (label === "out" && result.currCase.in === null) {
    error(fileLineNo, "must include an 'in' block before an 'out' block.");
  }

  result.currLabel = label;
  result.currCase[label] = {
    "fileLineNo": fileLineNo,
    "lines": [],
    "cursor": null
  };
}

function handleCursorDxLine(result, block, fileLineNo, cursorDx, x) {
  if (result.currLabel === "out") {
    error(fileLineNo, "no ^ cursorDx annotation allowed in output block.");
  }
  if (block.cursor === null || block.cursor.cursorX !== x) {
    error(fileLineNo, "the ^ cursorDx annotation does not point to a cursor.");
  }

  block.cursor.cursorDx = cursorDx;
  return true;
}

function handleErrorLine(result, block, fileLineNo, name, x) {
  if (result.currLabel === "in") {
    error(fileLineNo, "no ^ error annotation allowed in input block.");
  }
  if (block.error) {
    error(fileLineNo, "only one ^ error annotation allowed in output block.");
  }

  var lineNo = block.lines.length-1;

  // shift x position back if previous line has cursor before our error caret
  if (block.cursor &&
      block.cursor.cursorLine === lineNo &&
      block.cursor.cursorX < x) {
    x--;
  }

  block.error = {
    "name": name,
    "lineNo": lineNo,
    "x": x,
  };
  return true;
}

function handleCursorPreviewScope(result, block, fileLineNo, x) {
  if (result.currLabel === "out") {
    error(fileLineNo, "no ^ previewCursorScope annotation allowed in output block.");
  }
  if (block.cursor === null || block.cursor.cursorX !== x) {
    error(fileLineNo, "the ^ previewCursorScope annotation does not point to a cursor.");
  }

  block.cursor.previewCursorScope = true;
  return true;
}

function handleCaretLine(result, block, fileLineNo, line) {
  var cursorDxMatch = /^\s*\^\s*cursorDx (-?\d+)\s*$/.exec(line);
  var cursorPreviewScopeMatch = /^\s*\^\spreviewCursorScope\s*$/.exec(line);
  var errorMatch = /^\s*\^\s*([a-z-]+)$/.exec(line);
  var x = line.indexOf("^");

  if (cursorDxMatch) {
    var cursorDx = parseInt(cursorDxMatch[1]);
    return handleCursorDxLine(result, block, fileLineNo, cursorDx, x);
  }
  else if (cursorPreviewScopeMatch) {
    return handleCursorPreviewScope(result, block, fileLineNo, x);
  }
  else if (errorMatch) {
    var name = errorMatch[1];
    return handleErrorLine(result, block, fileLineNo, name, x);
  }

  return false;
}

function handleCursor(block, fileLineNo, line) {
  var cursorX = line.indexOf("|");
  if (cursorX !== -1) {
    if (block.cursor !== null) {
      error(fileLineNo, "only one cursor allowed.  cursor already found at line", block.cursor.cursorLine);
    }

    var lineClean = line.split("|").join("");
    if (lineClean.length < line.length-1) {
      error(fileLineNo, "only one cursor allowed");
    }
    line = lineClean;

    block.cursor = {
      "cursorX": cursorX,
      "cursorLine": block.lines.length
    };
  }
  return line;
}

function parseLine_insideBlock(result, fileLineNo, line) {
  var block = result.currCase[result.currLabel];

  if (handleCaretLine(result, block, fileLineNo, line)) {
    return;
  }

  line = handleCursor(block, fileLineNo, line);
  block.lines.push(line);
}

function parseLine_default(result, fileLineNo, line) {
  return result;
}

function parseLine(result, fileLineNo, line) {

  var f;
  if (line === "```")                 { f = parseLine_endBlock; }
  else if (line.startsWith("```"))    { f = parseLine_startBlock; }
  else if (result.currLabel !== null) { f = parseLine_insideBlock; }
  else                                { f = parseLine_default; }

  return f(result, fileLineNo, line);
}

function parseText(text) {
  var lines = text.split("\n");
  var result = getInitialResult();
  var i;
  for (i=0; i<lines.length; i++) {
    parseLine(result, i, lines[i]);
  }

  if (result.currLabel !== null) {
    error("EOF", "code block not closed");
  }
  if (result.currCase.in !== null ||
      result.currCase.out !== null) {
    error("EOF", "test case 'out' block not completed");
  }

  return result.cases;
}

//------------------------------------------------------------------------------
// JSON builder
//------------------------------------------------------------------------------

var casesPath = __dirname;

function buildJson(name) {
  // JSON.stringify(data, null, "  ");
  var inFile = casesPath + "/" + name + ".md";
  var outFile = casesPath + "/" + name + ".json";
  var inText = fs.readFileSync(inFile, "utf8");

  console.log(
    "  compiling " +
    name + ".md" +
    "  -->  " +
    name + ".json"
  );

  var cases = parseText(inText);
  var outText = JSON.stringify(cases, null, "  ");
  fs.writeFileSync(outFile, outText);
}

function buildAll() {
  console.log("\nReading test cases described in Markdown and compiling to JSON...");
  buildJson("indent-mode");
  buildJson("paren-mode");
  console.log();
}

//------------------------------------------------------------------------------
// Exports and Entry
//------------------------------------------------------------------------------

// export api
exports.buildAll = buildAll;

// allow running this file directly with Node
if (require.main === module) {
  buildAll();
}
<|endoftext|>


<commit_before>/*
Copyright 2017 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";

import q from "q";
import {MatrixInMemoryStore} from "./memory";
import User from "../models/user";
import Room from "../models/room";
import {MatrixEvent} from "../models/event";
import utils from "../utils";

/**
 * This is an internal module. See {@link IndexedDBStore} for the public class.
 * @module store/indexeddb
 */

const VERSION = 1;
const WRITE_DELAY_MS = 1000 * 60; // once a minute

/**
 * Construct a new Indexed Database store backend. This requires a call to
 * connect() before this store can be used.
 * @constructor
 * @param {Object} indexedDBInterface The Indexed DB interface e.g
 * window.indexedDB
 */
const IndexedDBStoreBackend = function IndexedDBStoreBackend(indexedDBInterface) {
    this.indexedDB = indexedDBInterface;
    this.db = null;
};

IndexedDBStoreBackend.prototype = {
    /**
     * Attempt to connect to the database. This can fail if the user does not
     * grant permission.
     * @return {Promise} Resolves if successfully connected.
     */
    connect: function() {
        if (this.db) {
            return q();
        }
        const req = this.indexedDB.open("matrix-js-sdk", VERSION);
        req.onupgradeneeded = (ev) => {
            const db = ev.target.result;
            const oldVersion = ev.oldVersion;
            if (oldVersion < 1) { // The database did not previously exist.
                createDatabase(db);
            }
            // Expand as needed.
        };
        return promiseifyRequest(req).then((ev) => {
            this.db = ev.target.result;
        });
    },

    /**
     * Clear the entire database. This should be used when logging out of a client
     * to prevent mixing data between accounts.
     * @return {Promise} Resolved when the database is cleared.
     */
    clearDatabase: function() {
        return promiseifyRequest(this.indexedDB.deleteDatabase("matrix-js-sdk"));
    },

    /**
     * Persist a list of Room objects. Rooms with the same 'roomId' will be replaced.
     * @param {Room[]} rooms An array of rooms
     * @return {Promise} Resolves if the rooms were persisted.
     */
    persistRooms: function(rooms) {
        return this._upsert("rooms", rooms);
    },

    /**
     * Persist a sync token. This will replace any existing sync token.
     * @param {string} syncToken The token to persist.
     * @return {Promise} Resolves if the token was persisted.
     */
    persistSyncToken: function(syncToken) {
        const obj = {
            clobber: "-", // constant key so will always clobber
            syncToken: syncToken,
        };
        return this._upsert("config", [obj]);
    },

    /**
     * Persist a list of account data events. Events with the same 'type' will
     * be replaced.
     * @param {MatrixEvent[]} accountData An array of user-scoped account data events
     * @return {Promise} Resolves if the events were persisted.
     */
    persistAccountData: function(accountData) {
        return q.try(() => {
            const txn = this.db.transaction(["accountData"], "readwrite");
            const store = txn.objectStore("accountData");
            for (let i = 0; i < accountData.length; i++) {
                store.put(accountData[i].event); // put == UPSERT
            }
            return promiseifyTxn(txn);
        });
    },

    /**
     * Persist a list of User objects. Users with the same 'userId' will be
     * replaced.
     * @param {User[]} users An array of users
     * @return {Promise} Resolves if the users were persisted.
     */
    persistUsers: function(users) {
        console.log("persistUsers =>", users);
        return this._upsert("users", users);
    },

    /**
     * Load all the users from the database. This is not cached.
     * @return {Promise<User[]>} A list of users.
     */
    loadUsers: function() {
        return this._deserializeAll("users", User);
    },

    /**
     * Load all the rooms from the database. This is not cached.
     * @return {Promise<Room[]>} A list of rooms.
     */
    loadRooms: function() {
        return this._deserializeAll("rooms", Room);
    },

    /**
     * Load all the account data events from the database. This is not cached.
     * @return {Promise<MatrixEvent[]>} A list of events.
     */
    loadAccountData: function() {
        return q.try(() => {
            const txn = this.db.transaction(["accountData"], "readonly");
            const store = txn.objectStore("accountData");
            return selectQuery(store, undefined, (cursor) => {
                return new MatrixEvent(cursor.value);
            });
        });
    },

    /**
     * Load the sync token from the database.
     * @return {Promise<?string>} The sync token
     */
    loadSyncToken: function() {
        return q.try(() => {
            const txn = this.db.transaction(["config"], "readonly");
            const store = txn.objectStore("config");
            const results = selectQuery(store, undefined, (cursor) => {
                return cursor.value;
            });
            if (results.length > 1) {
                console.warn("loadSyncToken: More than 1 config row found.");
            }
            return (results.length > 0 ? results[0].syncToken : null);
        });
    },

    _upsert: function(storeName, rows) {
        return q.try(() => {
            const txn = this.db.transaction([storeName], "readwrite");
            const store = txn.objectStore(storeName);
            for (let i = 0; i < rows.length; i++) {
                if (typeof rows[i].serialize === "function") {
                    store.put(rows[i].serialize()); // put == UPSERT
                } else {
                    store.put(rows[i]); // put == UPSERT
                }
            }
            return promiseifyTxn(txn);
        });
    },

    _deserializeAll: function(storeName, Cls) {
        return q.try(() => {
            const txn = this.db.transaction([storeName], "readonly");
            const store = txn.objectStore(storeName);
            return selectQuery(store, undefined, (cursor) => {
                return Cls.deserialize(cursor.value);
            });
        });
    },
};

/**
 * Construct a new Indexed Database store, which extends MatrixInMemoryStore.
 *
 * This store functions like a MatrixInMemoryStore except it periodically persists
 * the contents of the store to an IndexedDB backend.
 *
 * All data is still kept in-memory but can be loaded from disk by calling
 * startup(). This can make startup times quicker as a complete
 * sync from the server is not required. This does not reduce memory usage as all
 * the data is eagerly fetched when startup() is called.
 *

 * let opts = { localStorage: window.localStorage };
 * let store = new IndexedDBStore(new IndexedDBStoreBackend(window.indexedDB), opts);
 * await store.startup(); // load from indexed db
 * let client = sdk.createClient({
 *     store: store,
 * });
 * client.startClient();
 * client.on("sync", function(state, prevState, data) {
 *     if (state === "PREPARED") {
 *         console.log("Started up, now with go faster stripes!");
 *     }
 * });
 *

 *
 * @constructor
 * @extends MatrixInMemoryStore
 * @param {IndexedDBStoreBackend} backend The indexed db backend instance.
 * @param {Object=} opts Options for MatrixInMemoryStore.
 * @prop {IndexedDBStoreBackend} backend The backend instance. Call through to
 * this API if you need to perform specific indexeddb actions like deleting the
 * database.
 */
const IndexedDBStore = function IndexedDBStore(backend, opts) {
    MatrixInMemoryStore.call(this, opts);
    this.backend = backend;
    this.startedUp = false;
    this._syncTs = Date.now(); // updated when writes to the database are performed

    // internal structs to determine deltas for syncs to the database.
    this._userModifiedMap = {
        // user_id : timestamp
    };
};
utils.inherits(IndexedDBStore, MatrixInMemoryStore);

/**
 * @return {Promise} Resolved when loaded from indexed db.
  */
IndexedDBStore.prototype.startup = function() {
    if (this.startedUp) {
        return q();
    }
    return this.backend.connect().then(() => {
        return q.all([
            this.backend.loadUsers(),
            this.backend.loadAccountData(),
            this.backend.loadRooms(),
            this.backend.loadSyncToken(),
        ]);
    }).then((values) => {
        console.log("Loaded data from database. Reticulating splines...");
        const [users, accountData, rooms, syncToken] = values;
        users.forEach((u) => {
            this._userModifiedMap[u.userId] = u.getLastModifiedTime();
            this.storeUser(u);
        });
        this.storeAccountDataEvents(accountData);
        rooms.forEach((r) => {
            this.storeRoom(r);
        });
        this._syncTs = Date.now(); // pretend we've written so we don't rewrite
        this.setSyncToken(syncToken);
    });
};

/**
 * Set a new sync token and possibly write to the database.
 * Overrides MatrixInMemoryStore.
 * @param {string} token The new sync token
 * @return {?Promise} A promise if this sync token triggered a write to the
 * database, else null. Promise resolves after the write completes.
 */
IndexedDBStore.prototype.setSyncToken = function(token) {
    MatrixInMemoryStore.prototype.setSyncToken.call(this, token);
    const now = Date.now();
    if (now - this._syncTs > WRITE_DELAY_MS) {
        return this._syncToDatabase().catch((err) => {console.error("sync fail:", err);});
    }
    return null;
};

IndexedDBStore.prototype._syncToDatabase = function() {
    console.log("_syncToDatabase");
    this._syncTs = Date.now(); // set now to guard against multi-writes

    // work out changed users (this doesn't handle deletions but you
    // can't 'delete' users as they are just presence events).
    const changedUsers = this.getUsers().filter((user) => {
        return this._userModifiedMap[user.userId] !== user.getLastModifiedTime();
    });
    changedUsers.forEach((u) => { // update times
        this._userModifiedMap[u.userId] = u.getLastModifiedTime();
    });

    return this.backend.persistUsers(changedUsers);
};

function createDatabase(db) {
    // Make room store, clobber based on room ID. (roomId property of Room objects)
    db.createObjectStore("rooms", { keyPath: ["roomId"] });

    // Make user store, clobber based on user ID. (userId property of User objects)
    db.createObjectStore("users", { keyPath: ["userId"] });

    // Make account data store, clobber based on event type.
    // (event.type property of MatrixEvent objects)
    db.createObjectStore("accountData", { keyPath: ["event.type"] });

    // Make configuration store (sync tokens, etc), always clobber (const key).
    db.createObjectStore("config", { keyPath: ["clobber"] });
}

/**
 * Helper method to collect results from a Cursor and promiseify it.
 * @param {ObjectStore|Index} store The store to perform openCursor on.
 * @param {IDBKeyRange=} keyRange Optional key range to apply on the cursor.
 * @param {Function} resultMapper A function which is repeatedly called with a
 * Cursor.
 * Return the data you want to keep.
 * @return {Promise<T[]>} Resolves to an array of whatever you returned from
 * resultMapper.
 */
function selectQuery(store, keyRange, resultMapper) {
    const query = store.openCursor(keyRange);
    return q.Promise((resolve, reject) => { /*eslint new-cap: 0*/
        const results = [];
        query.onerror = (event) => {
            reject(new Error("Query failed: " + event.target.errorCode));
        };
        // collect results
        query.onsuccess = (event) => {
            const cursor = event.target.result;
            if (!cursor) {
                resolve(results);
                return; // end of results
            }
            results.push(resultMapper(cursor));
            cursor.continue();
        };
    });
}

function promiseifyTxn(txn) {
    return new q.Promise((resolve, reject) => {
        txn.oncomplete = function(event) {
            console.log("txn success:", event);
            resolve(event);
        };
        txn.onerror = function(event) {
            console.error("txn fail:", event);
            reject(event);
        };
    });
}

function promiseifyRequest(req) {
    return new q.Promise((resolve, reject) => {
        req.onsuccess = function(event) {
            console.log("req success:", event);
            resolve(event);
        };
        req.onerror = function(event) {
            console.error("req fail:", event);
            reject(event);
        };
    });
}

module.exports.IndexedDBStore = IndexedDBStore;
module.exports.IndexedDBStoreBackend = IndexedDBStoreBackend;
<commit_msg>Load/Store account data events<commit_after>/*
Copyright 2017 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";

import q from "q";
import {MatrixInMemoryStore} from "./memory";
import User from "../models/user";
import Room from "../models/room";
import {MatrixEvent} from "../models/event";
import utils from "../utils";

/**
 * This is an internal module. See {@link IndexedDBStore} for the public class.
 * @module store/indexeddb
 */

const VERSION = 1;
const WRITE_DELAY_MS = 1000 * 60; // once a minute

/**
 * Construct a new Indexed Database store backend. This requires a call to
 * connect() before this store can be used.
 * @constructor
 * @param {Object} indexedDBInterface The Indexed DB interface e.g
 * window.indexedDB
 */
const IndexedDBStoreBackend = function IndexedDBStoreBackend(indexedDBInterface) {
    this.indexedDB = indexedDBInterface;
    this.db = null;
};

IndexedDBStoreBackend.prototype = {
    /**
     * Attempt to connect to the database. This can fail if the user does not
     * grant permission.
     * @return {Promise} Resolves if successfully connected.
     */
    connect: function() {
        if (this.db) {
            return q();
        }
        const req = this.indexedDB.open("matrix-js-sdk", VERSION);
        req.onupgradeneeded = (ev) => {
            const db = ev.target.result;
            const oldVersion = ev.oldVersion;
            if (oldVersion < 1) { // The database did not previously exist.
                createDatabase(db);
            }
            // Expand as needed.
        };
        return promiseifyRequest(req).then((ev) => {
            this.db = ev.target.result;
        });
    },

    /**
     * Clear the entire database. This should be used when logging out of a client
     * to prevent mixing data between accounts.
     * @return {Promise} Resolved when the database is cleared.
     */
    clearDatabase: function() {
        return promiseifyRequest(this.indexedDB.deleteDatabase("matrix-js-sdk"));
    },

    /**
     * Persist a list of Room objects. Rooms with the same 'roomId' will be replaced.
     * @param {Room[]} rooms An array of rooms
     * @return {Promise} Resolves if the rooms were persisted.
     */
    persistRooms: function(rooms) {
        return this._upsert("rooms", rooms);
    },

    /**
     * Persist a sync token. This will replace any existing sync token.
     * @param {string} syncToken The token to persist.
     * @return {Promise} Resolves if the token was persisted.
     */
    persistSyncToken: function(syncToken) {
        const obj = {
            clobber: "-", // constant key so will always clobber
            syncToken: syncToken,
        };
        return this._upsert("config", [obj]);
    },

    /**
     * Persist a list of account data events. Events with the same 'type' will
     * be replaced.
     * @param {MatrixEvent[]} accountData An array of user-scoped account data events
     * @return {Promise} Resolves if the events were persisted.
     */
    persistAccountData: function(accountData) {
        return q.try(() => {
            const txn = this.db.transaction(["accountData"], "readwrite");
            const store = txn.objectStore("accountData");
            for (let i = 0; i < accountData.length; i++) {
                store.put(accountData[i].event); // put == UPSERT
            }
            return promiseifyTxn(txn);
        });
    },

    /**
     * Persist a list of User objects. Users with the same 'userId' will be
     * replaced.
     * @param {User[]} users An array of users
     * @return {Promise} Resolves if the users were persisted.
     */
    persistUsers: function(users) {
        console.log("persistUsers =>", users);
        return this._upsert("users", users);
    },

    /**
     * Load all the users from the database. This is not cached.
     * @return {Promise<User[]>} A list of users.
     */
    loadUsers: function() {
        return this._deserializeAll("users", User);
    },

    /**
     * Load all the rooms from the database. This is not cached.
     * @return {Promise<Room[]>} A list of rooms.
     */
    loadRooms: function() {
        return this._deserializeAll("rooms", Room);
    },

    /**
     * Load all the account data events from the database. This is not cached.
     * @return {Promise<MatrixEvent[]>} A list of events.
     */
    loadAccountData: function() {
        return q.try(() => {
            const txn = this.db.transaction(["accountData"], "readonly");
            const store = txn.objectStore("accountData");
            return selectQuery(store, undefined, (cursor) => {
                return new MatrixEvent(cursor.value);
            });
        });
    },

    /**
     * Load the sync token from the database.
     * @return {Promise<?string>} The sync token
     */
    loadSyncToken: function() {
        return q.try(() => {
            const txn = this.db.transaction(["config"], "readonly");
            const store = txn.objectStore("config");
            const results = selectQuery(store, undefined, (cursor) => {
                return cursor.value;
            });
            if (results.length > 1) {
                console.warn("loadSyncToken: More than 1 config row found.");
            }
            return (results.length > 0 ? results[0].syncToken : null);
        });
    },

    _upsert: function(storeName, rows) {
        return q.try(() => {
            const txn = this.db.transaction([storeName], "readwrite");
            const store = txn.objectStore(storeName);
            for (let i = 0; i < rows.length; i++) {
                if (typeof rows[i].serialize === "function") {
                    store.put(rows[i].serialize()); // put == UPSERT
                } else {
                    store.put(rows[i]); // put == UPSERT
                }
            }
            return promiseifyTxn(txn);
        });
    },

    _deserializeAll: function(storeName, Cls) {
        return q.try(() => {
            const txn = this.db.transaction([storeName], "readonly");
            const store = txn.objectStore(storeName);
            return selectQuery(store, undefined, (cursor) => {
                return Cls.deserialize(cursor.value);
            });
        });
    },
};

/**
 * Construct a new Indexed Database store, which extends MatrixInMemoryStore.
 *
 * This store functions like a MatrixInMemoryStore except it periodically persists
 * the contents of the store to an IndexedDB backend.
 *
 * All data is still kept in-memory but can be loaded from disk by calling
 * startup(). This can make startup times quicker as a complete
 * sync from the server is not required. This does not reduce memory usage as all
 * the data is eagerly fetched when startup() is called.
 *

 * let opts = { localStorage: window.localStorage };
 * let store = new IndexedDBStore(new IndexedDBStoreBackend(window.indexedDB), opts);
 * await store.startup(); // load from indexed db
 * let client = sdk.createClient({
 *     store: store,
 * });
 * client.startClient();
 * client.on("sync", function(state, prevState, data) {
 *     if (state === "PREPARED") {
 *         console.log("Started up, now with go faster stripes!");
 *     }
 * });
 *

 *
 * @constructor
 * @extends MatrixInMemoryStore
 * @param {IndexedDBStoreBackend} backend The indexed db backend instance.
 * @param {Object=} opts Options for MatrixInMemoryStore.
 * @prop {IndexedDBStoreBackend} backend The backend instance. Call through to
 * this API if you need to perform specific indexeddb actions like deleting the
 * database.
 */
const IndexedDBStore = function IndexedDBStore(backend, opts) {
    MatrixInMemoryStore.call(this, opts);
    this.backend = backend;
    this.startedUp = false;
    this._syncTs = Date.now(); // updated when writes to the database are performed

    // internal structs to determine deltas for syncs to the database.
    this._userModifiedMap = {
        // user_id : timestamp
    };
};
utils.inherits(IndexedDBStore, MatrixInMemoryStore);

/**
 * @return {Promise} Resolved when loaded from indexed db.
  */
IndexedDBStore.prototype.startup = function() {
    if (this.startedUp) {
        return q();
    }
    return this.backend.connect().then(() => {
        return q.all([
            this.backend.loadUsers(),
            this.backend.loadAccountData(),
            this.backend.loadRooms(),
            this.backend.loadSyncToken(),
        ]);
    }).then((values) => {
        const [users, accountData, rooms, syncToken] = values;
        console.log("Loaded data from database. Reticulating splines...",accountData, users);
        users.forEach((u) => {
            this._userModifiedMap[u.userId] = u.getLastModifiedTime();
            this.storeUser(u);
        });
        this.storeAccountDataEvents(accountData);
        rooms.forEach((r) => {
            this.storeRoom(r);
        });
        this._syncTs = Date.now(); // pretend we've written so we don't rewrite
        this.setSyncToken(syncToken);
    });
};

/**
 * Set a new sync token and possibly write to the database.
 * Overrides MatrixInMemoryStore.
 * @param {string} token The new sync token
 * @return {?Promise} A promise if this sync token triggered a write to the
 * database, else null. Promise resolves after the write completes.
 */
IndexedDBStore.prototype.setSyncToken = function(token) {
    MatrixInMemoryStore.prototype.setSyncToken.call(this, token);
    const now = Date.now();
    if (now - this._syncTs > WRITE_DELAY_MS) {
        return this._syncToDatabase().catch((err) => {console.error("sync fail:", err);});
    }
    return null;
};

IndexedDBStore.prototype._syncToDatabase = function() {
    console.log("_syncToDatabase");
    this._syncTs = Date.now(); // set now to guard against multi-writes

    // work out changed users (this doesn't handle deletions but you
    // can't 'delete' users as they are just presence events).
    const changedUsers = this.getUsers().filter((user) => {
        return this._userModifiedMap[user.userId] !== user.getLastModifiedTime();
    });
    changedUsers.forEach((u) => { // update times
        this._userModifiedMap[u.userId] = u.getLastModifiedTime();
    });

    // TODO: work out changed account data events. They don't have timestamps or IDs.
    // so we'll need to hook into storeAccountDataEvents instead to catch them when
    // they update from /sync
    const changedAccountData = Object.keys(this.accountData).map((etype) => this.accountData[etype]);

    return q.all([
        this.backend.persistUsers(changedUsers),
        this.backend.persistAccountData(changedAccountData),
    ]);
};

function createDatabase(db) {
    // Make room store, clobber based on room ID. (roomId property of Room objects)
    db.createObjectStore("rooms", { keyPath: ["roomId"] });

    // Make user store, clobber based on user ID. (userId property of User objects)
    db.createObjectStore("users", { keyPath: ["userId"] });

    // Make account data store, clobber based on event type.
    // (event.type property of MatrixEvent objects)
    db.createObjectStore("accountData", { keyPath: ["type"] });

    // Make configuration store (sync tokens, etc), always clobber (const key).
    db.createObjectStore("config", { keyPath: ["clobber"] });
}

/**
 * Helper method to collect results from a Cursor and promiseify it.
 * @param {ObjectStore|Index} store The store to perform openCursor on.
 * @param {IDBKeyRange=} keyRange Optional key range to apply on the cursor.
 * @param {Function} resultMapper A function which is repeatedly called with a
 * Cursor.
 * Return the data you want to keep.
 * @return {Promise<T[]>} Resolves to an array of whatever you returned from
 * resultMapper.
 */
function selectQuery(store, keyRange, resultMapper) {
    const query = store.openCursor(keyRange);
    return q.Promise((resolve, reject) => { /*eslint new-cap: 0*/
        const results = [];
        query.onerror = (event) => {
            reject(new Error("Query failed: " + event.target.errorCode));
        };
        // collect results
        query.onsuccess = (event) => {
            const cursor = event.target.result;
            if (!cursor) {
                resolve(results);
                return; // end of results
            }
            results.push(resultMapper(cursor));
            cursor.continue();
        };
    });
}

function promiseifyTxn(txn) {
    return new q.Promise((resolve, reject) => {
        txn.oncomplete = function(event) {
            console.log("txn success:", event);
            resolve(event);
        };
        txn.onerror = function(event) {
            console.error("txn fail:", event);
            reject(event);
        };
    });
}

function promiseifyRequest(req) {
    return new q.Promise((resolve, reject) => {
        req.onsuccess = function(event) {
            console.log("req success:", event);
            resolve(event);
        };
        req.onerror = function(event) {
            console.error("req fail:", event);
            reject(event);
        };
    });
}

module.exports.IndexedDBStore = IndexedDBStore;
module.exports.IndexedDBStoreBackend = IndexedDBStoreBackend;
<|endoftext|>


<commit_before>import update from 'immutability-helper';

import { default as api } from '../utils/api';

// ------------------------------------
// Constants
// ------------------------------------
export const RECEIVE_ATTRIBUTE_TYPES = 'ui/RECEIVE_ATTRIBUTE_TYPES';

export const CLOSE_MESSAGE = 'ui/CLOSE_MESSAGE';
export const DISPLAY_MESSAGE = 'ui/DISPLAY_MESSAGE';

// ------------------------------------
// Actions
// ------------------------------------
export function receiveAttributeTypes (attributes, validationRules) {
  const attributeTypeList = {};
  attributes.results.map(result => {
    if (result.values) {
      let required;
      validationRules.record.required.map(rule => {
        if (rule === result.identifier) {
          required = true;
        }
      });
      if (required !== true) {
        required = false;
      }
      attributeTypeList[result.identifier] = {
        name: result.name,
        values: result.values,
        required
      };
    }
  });
  return {
    type: RECEIVE_ATTRIBUTE_TYPES,
    attributeTypeList
  };
}

export function displayMessage (message) {
  return {
    type: DISPLAY_MESSAGE,
    message
  };
}

export function closeMessage () {
  return {
    type: CLOSE_MESSAGE
  };
}

export function fetchAttributeTypes () {
  return function (dispatch) {
    return api.get('attribute/schemas')
      .then(response => response.json())
      .then(validationRules => {
        return api.get('attribute')
          .then(response => response.json())
          .then(json =>
            dispatch(receiveAttributeTypes(json, validationRules)));
      });
  };
}

export const actions = {
  closeMessage,
  fetchAttributeTypes,
  receiveAttributeTypes
};

// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
  [RECEIVE_ATTRIBUTE_TYPES]: (state, action) => {
    const recordTypes = action.attributeTypeList.RecordType;
    const recordTypeList = {};
    recordTypes.values.map(result => {
      const trimmedResult = result.id.replace(/-/g, '');
      recordTypeList[trimmedResult] = result.value;
    });
    delete action.attributeTypeList.RecordType;
    return update(state, {
      attributeTypes: { $set: action.attributeTypeList },
      recordTypes: { $set: recordTypeList }
    });
  },
  [DISPLAY_MESSAGE]: (state, action) => {
    return update(state, {
      message: {
        active: { $set: true },
        text: { $set: action.message },
        success: { $set: true }
      }
    });
  },
  [CLOSE_MESSAGE]: (state, action) => {
    return update(state, {
      message: {
        active: { $set: false },
        text: { $set: '' },
        success: { $set: false }
      }
    });
  }
};

// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {
  isFetching: false,
  recordTypes: {},
  attributeTypes: {},
  message: {
    active: false,
    text: '',
    success: false
  }
};

export default function uiReducer (state = initialState, action) {
  const handler = ACTION_HANDLERS[action.type];
  return handler ? handler(state, action) : state;
}
<commit_msg>Refactor display_message<commit_after>import update from 'immutability-helper';

import { default as api } from '../utils/api';

// ------------------------------------
// Constants
// ------------------------------------
export const RECEIVE_ATTRIBUTE_TYPES = 'ui/RECEIVE_ATTRIBUTE_TYPES';

export const CLOSE_MESSAGE = 'ui/CLOSE_MESSAGE';
export const DISPLAY_MESSAGE = 'ui/DISPLAY_MESSAGE';

// ------------------------------------
// Actions
// ------------------------------------
export function receiveAttributeTypes (attributes, validationRules) {
  const attributeTypeList = {};
  attributes.results.map(result => {
    if (result.values) {
      let required;
      validationRules.record.required.map(rule => {
        if (rule === result.identifier) {
          required = true;
        }
      });
      if (required !== true) {
        required = false;
      }
      attributeTypeList[result.identifier] = {
        name: result.name,
        values: result.values,
        required
      };
    }
  });
  return {
    type: RECEIVE_ATTRIBUTE_TYPES,
    attributeTypeList
  };
}

export function displayMessage (message) {
  return {
    type: DISPLAY_MESSAGE,
    message
  };
}

export function closeMessage () {
  return {
    type: CLOSE_MESSAGE
  };
}

export function fetchAttributeTypes () {
  return function (dispatch) {
    return api.get('attribute/schemas')
      .then(response => response.json())
      .then(validationRules => {
        return api.get('attribute')
          .then(response => response.json())
          .then(json =>
            dispatch(receiveAttributeTypes(json, validationRules)));
      });
  };
}

export const actions = {
  closeMessage,
  fetchAttributeTypes,
  receiveAttributeTypes
};

// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
  [RECEIVE_ATTRIBUTE_TYPES]: (state, action) => {
    const recordTypes = action.attributeTypeList.RecordType;
    const recordTypeList = {};
    recordTypes.values.map(result => {
      const trimmedResult = result.id.replace(/-/g, '');
      recordTypeList[trimmedResult] = result.value;
    });
    delete action.attributeTypeList.RecordType;
    return update(state, {
      attributeTypes: { $set: action.attributeTypeList },
      recordTypes: { $set: recordTypeList }
    });
  },
  [DISPLAY_MESSAGE]: (state, action) => {
    return update(state, {
      message: {
        active: { $set: true },
        text: { $set: action.message.text },
        success: { $set: action.message.success }
      }
    });
  },
  [CLOSE_MESSAGE]: (state, action) => {
    return update(state, {
      message: {
        active: { $set: false },
        text: { $set: '' },
        success: { $set: false }
      }
    });
  }
};

// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {
  isFetching: false,
  recordTypes: {},
  attributeTypes: {},
  message: {
    active: false,
    text: '',
    success: false
  }
};

export default function uiReducer (state = initialState, action) {
  const handler = ACTION_HANDLERS[action.type];
  return handler ? handler(state, action) : state;
}
<|endoftext|>