<commit_before>var sqlite = require('sqlite').sqlite3_bindings;
var Step = require('step');
var crypto = require('crypto');

function MBTiles(filename, options) {
  this.filename = filename;
  this.options = options || {};
  this.db = new sqlite.Database();
}

function nextStep(next) {
    return function(err, statement) {
        if (err) {
            console.warn(err.message);
            console.trace();
            return;
        }
        statement.step(next);
    };
}

// Setup schema, indices, views for a new mbtiles database.
MBTiles.prototype.setup = function(callback) {
    var that = this;
    Step(
        function() {
            that.db.open(that.filename, this);
        },
        // # Map
        function() {
            that.db.prepare(
                'CREATE TABLE IF NOT EXISTS map ('
                + 'zoom_level integer, '
                + 'tile_column integer, '
                + 'tile_row integer, '
                + 'tile_id TEXT, '
                + 'grid_id TEXT);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE UNIQUE INDEX IF NOT EXISTS map_index on map '
                + '(zoom_level, tile_column, tile_row);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE TABLE IF NOT EXISTS grid_key ('
                + 'grid_id TEXT, '
                + 'key_name TEXT);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE UNIQUE INDEX IF NOT EXISTS grid_key_lookup on grid_key '
                + '(grid_id, key_name);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE TABLE IF NOT EXISTS keymap ('
                + 'key_name TEXT, '
                + 'key_json TEXT);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE UNIQUE INDEX IF NOT EXISTS keymap_lookup on keymap '
                + '(key_name);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE TABLE IF NOT EXISTS grid_utfgrid ('
                + 'grid_id TEXT, '
                + 'grid_utfgrid TEXT);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE UNIQUE INDEX IF NOT EXISTS grid_utfgrid_lookup on grid_utfgrid '
                + '(grid_id);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE TABLE IF NOT EXISTS images ('
                + 'tile_data blob, '
                + 'tile_id text);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE UNIQUE INDEX IF NOT EXISTS images_id on images '
                + '(tile_id);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE view IF NOT EXISTS tiles AS SELECT '
                + 'map.zoom_level AS zoom_level, '
                + 'map.tile_column as tile_column, '
                + 'map.tile_row as tile_row, '
                + 'images.tile_data as tile_data, '
                + 'grid_utfgrid.grid_utfgrid as grid '
                + 'FROM map '
                + 'JOIN images ON images.tile_id = map.tile_id '
                + 'JOIN grid_utfgrid ON grid_utfgrid.grid_id = map.grid_id;',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE TABLE IF NOT EXISTS metadata ('
                + 'name text,'
                + 'value text);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE UNIQUE INDEX IF NOT EXISTS name on metadata '
                + '(name);',
                nextStep(this)
            );
        },
        // Set the synchronous flag to NORMAL for (much) faster inserts.
        // See http://www.sqlite.org/pragma.html#pragma_synchronous
        function() {
            that.db.prepare(
                'PRAGMA synchronous = 1',
                nextStep(this)
            );
        },
        function() {
            callback();
        }
    );
};

// Insert a set of tiles into an mbtiles database.
//
// - @param {Array} tiles array of tile [z, x, y] coordinates to be inserted.
// - @param {Array} renders array of tile images to be inserted. Indices should
//   correspond to the coordinates of the tiles array.
// - @param {Boolean} compress whether to "compress" the mbtiles database by
//   inserting only unique tile renders.
// - @param {Function} callback
MBTiles.prototype.insertGrids = function(tiles, renders, compress, callback) {
    var that = this,
        map = [],
        grids = [],
        grid_keys = [],
        ids = [],
        inserted = [];
    for (var i = 0; i < tiles.length; i++) {
        var tile_id;
        if (compress) {
            // Generate ID from MD5 hash of actual image data.
            grid_id = crypto.createHash('md5').update(renders[i][0]).digest('hex');
        } else {
            // Generate ID from tile coordinates (unique).
            grid_id = crypto.createHash('md5').update(JSON.stringify(tiles[i])).digest('hex');
        }
        if (ids.indexOf(grid_id) === -1) {
            ids.push(grid_id);
            grids.push({
                grid_id: grid_id,
                grid_utfgrid: renders[i][0]
            });
        }
        renders[i][2].keys.forEach(function(k) {
            grid_keys.push({
                grid_id: grid_id,
                key_name: k
            });
        });
        map.push({
            grid_id: grid_id,
            zoom_level: tiles[i][0],
            tile_column: tiles[i][1],
            tile_row: tiles[i][2]
        });
    }

    Step(
        function() {
            var group = this.group();
            for (var i = 0; i < grids.length; i++) {
                that.insertGrid(grids[i], group());
            }
            for (var i = 0; i < grid_keys.length; i++) {
                that.insertGridKey(grid_keys[i], group());
            }
            for (var i = 0; i < map.length; i++) {
                that.insertGridTile(map[i], group());
            }
        },
        function(err) {
            callback(err);
        }
    );
};

// Insert a set of tiles into an mbtiles database.
//
// - @param {Array} tiles array of tile [z, x, y] coordinates to be inserted.
// - @param {Array} renders array of tile images to be inserted. Indices should
//   correspond to the coordinates of the tiles array.
// - @param {Boolean} compress whether to "compress" the mbtiles database by
//   inserting only unique tile renders.
// - @param {Function} callback
MBTiles.prototype.insertTiles = function(tiles, renders, compress, callback) {
    var that = this,
        map = [],
        images = [],
        ids = [],
        inserted = [];
    for (var i = 0; i < tiles.length; i++) {
        var tile_id;
        if (compress) {
            // Generate ID from MD5 hash of actual image data.
            tile_id = crypto.createHash('md5').update(renders[i]).digest('hex');
        } else {
            // Generate ID from tile coordinates (unique).
            tile_id = crypto.createHash('md5').update(JSON.stringify(tiles[i])).digest('hex');
        }
        if (ids.indexOf(tile_id) === -1) {
            ids.push(tile_id);
            images.push({
                tile_id: tile_id,
                tile_data: renders[i]
            });
        }
        map.push({
            tile_id: tile_id,
            zoom_level: tiles[i][0],
            tile_column: tiles[i][1],
            tile_row: tiles[i][2]
        });
    }

    Step(
        function() {
            var group = this.group();
            for (var i = 0; i < images.length; i++) {
                that.insertImage(images[i], group());
            }
            for (var i = 0; i < map.length; i++) {
                that.insertTile(map[i], group());
            }
        },
        function(err) {
            callback(err);
        }
    );
};

MBTiles.prototype.insertGridKey = function(grid_key, callback) {
    var that = this;
    that.db.prepare(
        'INSERT OR ABORT INTO grid_key ('
        + 'grid_id, '
        + 'key_name) '
        + 'VALUES (?, ?);',
        function(err, statement) {
            if (statement) {
                callback(err);
                statement.bind(1, grid_key.grid_id);
                statement.bind(2, grid_key.key_name);
                statement.step(function(err) {
                    callback(err);
                });
            }
            else {
                callback(err);
            }
        }
    );
};

// Insert a single feature into the grid_data table,
// with the key/axis of `key`
MBTiles.prototype.insertGridData = function(data, key_name, callback) {
    var that = this;
    that.db.prepare(
        'INSERT OR ABORT INTO keymap ('
        + 'key_name, '
        + 'key_json) '
        + 'VALUES (?, ?);',
        function(err, statement) {
            if (statement) {
                callback(err);
                statement.bind(1, data[key_name]);
                statement.bind(2, JSON.stringify(data));
                statement.step(function(err) {
                    callback(err);
                });
            }
            else {
                callback(err);
            }
        }
    );
};

// Insert a single tile into the mbtiles database.
//
// - @param {Object} tile tile object to be inserted.
// - @param {Function} callback
MBTiles.prototype.insertTile = function(tile, callback) {
    var that = this;
    that.db.prepare(
        'INSERT INTO map ('
        + 'tile_id, '
        + 'zoom_level, '
        + 'tile_column, '
        + 'tile_row) '
        + 'VALUES (?, ?, ?, ?);',
        function(err, statement) {
            if (statement) {
                statement.bind(1, tile.tile_id);
                statement.bind(2, tile.zoom_level);
                statement.bind(3, tile.tile_column);
                statement.bind(4, tile.tile_row);
                statement.step(function(err) {
                    callback(err);
                });
            }
            else {
                callback(err);
            }
        }
    );
};

// Insert a single tile into the mbtiles database.
//
// - @param {Object} tile tile object to be inserted.
// - @param {Function} callback
MBTiles.prototype.insertGridTile = function(tile, callback) {
    // TODO: should update instead of inserting
    var that = this;
    that.db.prepare(
        'UPDATE OR REPLACE map SET grid_id = ? WHERE '
        + ' zoom_level = ? AND'
        + ' tile_column = ? AND'
        + ' tile_row = ?;',
        function(err, statement) {
            if (statement) {
                statement.bind(1, tile.grid_id);
                statement.bind(2, tile.zoom_level);
                statement.bind(3, tile.tile_column);
                statement.bind(4, tile.tile_row);
                statement.step(function(err) {
                    callback(err);
                });
            }
            else {
                callback(err);
            }
        }
    );
};

// Insert a single grid into the mbtiles database.
//
// - @param {Object} image object to be inserted.
// - @param {Function} callback
MBTiles.prototype.insertGrid = function(grid, callback) {
    var that = this;
    that.db.prepare(
        'INSERT OR ABORT INTO grid_utfgrid ('
        + 'grid_id, '
        + 'grid_utfgrid) '
        + 'VALUES (?, ?);',
        function(err, statement) {
            if (statement) {
                statement.bind(1, grid.grid_id);
                statement.bind(2, grid.grid_utfgrid);
                statement.step(function(err) {
                    callback(err);
                });
            } else {
                callback(err);
            }
        }
    );
};

// Insert a single image into the mbtiles database.
//
// @param {Object} image object to be inserted.
// @param {Function} callback
MBTiles.prototype.insertImage = function(image, callback) {
    var that = this;
    that.db.prepare(
        'INSERT OR ABORT INTO images ('
        + 'tile_id, '
        + 'tile_data) '
        + 'VALUES (?, ?);',
        function(err, statement) {
            if (statement) {
                statement.bind(1, image.tile_id);
                statement.bind(2, image.tile_data);
                statement.step(function(err) {
                    callback(err);
                });
            } else {
                callback(err);
            }
        }
    );
};

// Insert metadata into the mbtiles database.
//
// - @param {Object} metadata key, value hash of metadata to be inserted.
// - @param {Function} callback
MBTiles.prototype.metadata = function(metadata, callback) {
    var that = this;
    var insert = function(err, key, value, callback) {
        that.db.prepare(
            'INSERT INTO metadata (name, value) VALUES (?, ?);',
            function(err, statement) {
                statement.bind(1, key);
                statement.bind(2, value);
                statement.step(callback);
            }
        );
    };
    Step(
        function() {
            var group = this.group();
            for (var name in metadata) {
                insert(null, name, metadata[name], group());
            }
        },
        function(err) {
            callback(err);
        }
    );
};

// Select a tile from an mbtiles database.
//
// - @param {Number} x tile x coordinate.
// - @param {Number} y tile y coordinate.
// - @param {Number} z tile z coordinate.
// - @param {Function} callback
MBTiles.prototype.tile = function(x, y, z, callback) {
    var that = this;
    Step(
        function() {
            that.db.open(that.filename, this);
        },
        function() {
            var next = this;
            that.db.prepare(
                'SELECT tile_data FROM tiles WHERE '
                + 'zoom_level = ? AND '
                + 'tile_column = ? AND '
                + 'tile_row = ?;',
                function(err, statement) {
                    if (statement) {
                        statement.bind(1, z);
                        statement.bind(2, x);
                        statement.bind(3, y);
                        statement.step(next);
                    }
                    else {
                        next(err);
                    }
                }
            );
        },
        function(err, row) {
            that.db.close();
            if (!err && row && row.tile_data) {
                callback(err, row.tile_data);
            } else {
                callback(err);
            }
        }
    );
};

module.exports = MBTiles;
<commit_msg>Splitting view into tiles and grids<commit_after>var sqlite = require('sqlite').sqlite3_bindings;
var Step = require('step');
var crypto = require('crypto');

function MBTiles(filename, options) {
  this.filename = filename;
  this.options = options || {};
  this.db = new sqlite.Database();
}

function nextStep(next) {
    return function(err, statement) {
        if (err) {
            console.warn(err.message);
            console.trace();
            return;
        }
        statement.step(next);
    };
}

// Setup schema, indices, views for a new mbtiles database.
MBTiles.prototype.setup = function(callback) {
    var that = this;
    Step(
        function() {
            that.db.open(that.filename, this);
        },
        // # Map
        function() {
            that.db.prepare(
                'CREATE TABLE IF NOT EXISTS map ('
                + 'zoom_level integer, '
                + 'tile_column integer, '
                + 'tile_row integer, '
                + 'tile_id TEXT, '
                + 'grid_id TEXT);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE UNIQUE INDEX IF NOT EXISTS map_index on map '
                + '(zoom_level, tile_column, tile_row);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE TABLE IF NOT EXISTS grid_key ('
                + 'grid_id TEXT, '
                + 'key_name TEXT);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE UNIQUE INDEX IF NOT EXISTS grid_key_lookup on grid_key '
                + '(grid_id, key_name);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE TABLE IF NOT EXISTS keymap ('
                + 'key_name TEXT, '
                + 'key_json TEXT);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE UNIQUE INDEX IF NOT EXISTS keymap_lookup on keymap '
                + '(key_name);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE TABLE IF NOT EXISTS grid_utfgrid ('
                + 'grid_id TEXT, '
                + 'grid_utfgrid TEXT);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE UNIQUE INDEX IF NOT EXISTS grid_utfgrid_lookup on grid_utfgrid '
                + '(grid_id);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE TABLE IF NOT EXISTS images ('
                + 'tile_data blob, '
                + 'tile_id text);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE UNIQUE INDEX IF NOT EXISTS images_id on images '
                + '(tile_id);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE view IF NOT EXISTS tiles AS SELECT '
                + 'map.zoom_level AS zoom_level, '
                + 'map.tile_column as tile_column, '
                + 'map.tile_row as tile_row, '
                + 'images.tile_data as tile_data '
                + 'FROM map '
                + 'JOIN images ON images.tile_id = map.tile_id',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE view IF NOT EXISTS grids AS SELECT '
                + 'map.zoom_level AS zoom_level, '
                + 'map.tile_column as tile_column, '
                + 'map.tile_row as tile_row, '
                + 'grid_utfgrid.grid_utfgrid as grid '
                + 'FROM map '
                + 'JOIN grid_utfgrid ON grid_utfgrid.grid_id = map.grid_id',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE TABLE IF NOT EXISTS metadata ('
                + 'name text,'
                + 'value text);',
                nextStep(this)
            );
        },
        function() {
            that.db.prepare(
                'CREATE UNIQUE INDEX IF NOT EXISTS name on metadata '
                + '(name);',
                nextStep(this)
            );
        },
        // Set the synchronous flag to NORMAL for (much) faster inserts.
        // See http://www.sqlite.org/pragma.html#pragma_synchronous
        function() {
            that.db.prepare(
                'PRAGMA synchronous = 1',
                nextStep(this)
            );
        },
        function() {
            callback();
        }
    );
};

// Insert a set of tiles into an mbtiles database.
//
// - @param {Array} tiles array of tile [z, x, y] coordinates to be inserted.
// - @param {Array} renders array of tile images to be inserted. Indices should
//   correspond to the coordinates of the tiles array.
// - @param {Boolean} compress whether to "compress" the mbtiles database by
//   inserting only unique tile renders.
// - @param {Function} callback
MBTiles.prototype.insertGrids = function(tiles, renders, compress, callback) {
    var that = this,
        map = [],
        grids = [],
        grid_keys = [],
        ids = [],
        inserted = [];
    for (var i = 0; i < tiles.length; i++) {
        var tile_id;
        if (compress) {
            // Generate ID from MD5 hash of actual image data.
            grid_id = crypto.createHash('md5').update(renders[i][0]).digest('hex');
        } else {
            // Generate ID from tile coordinates (unique).
            grid_id = crypto.createHash('md5').update(JSON.stringify(tiles[i])).digest('hex');
        }
        if (ids.indexOf(grid_id) === -1) {
            ids.push(grid_id);
            grids.push({
                grid_id: grid_id,
                grid_utfgrid: renders[i][0]
            });
        }
        renders[i][2].keys.forEach(function(k) {
            grid_keys.push({
                grid_id: grid_id,
                key_name: k
            });
        });
        map.push({
            grid_id: grid_id,
            zoom_level: tiles[i][0],
            tile_column: tiles[i][1],
            tile_row: tiles[i][2]
        });
    }

    Step(
        function() {
            var group = this.group();
            for (var i = 0; i < grids.length; i++) {
                that.insertGrid(grids[i], group());
            }
            for (var i = 0; i < grid_keys.length; i++) {
                that.insertGridKey(grid_keys[i], group());
            }
            for (var i = 0; i < map.length; i++) {
                that.insertGridTile(map[i], group());
            }
        },
        function(err) {
            callback(err);
        }
    );
};

// Insert a set of tiles into an mbtiles database.
//
// - @param {Array} tiles array of tile [z, x, y] coordinates to be inserted.
// - @param {Array} renders array of tile images to be inserted. Indices should
//   correspond to the coordinates of the tiles array.
// - @param {Boolean} compress whether to "compress" the mbtiles database by
//   inserting only unique tile renders.
// - @param {Function} callback
MBTiles.prototype.insertTiles = function(tiles, renders, compress, callback) {
    var that = this,
        map = [],
        images = [],
        ids = [],
        inserted = [];
    for (var i = 0; i < tiles.length; i++) {
        var tile_id;
        if (compress) {
            // Generate ID from MD5 hash of actual image data.
            tile_id = crypto.createHash('md5').update(renders[i]).digest('hex');
        } else {
            // Generate ID from tile coordinates (unique).
            tile_id = crypto.createHash('md5').update(JSON.stringify(tiles[i])).digest('hex');
        }
        if (ids.indexOf(tile_id) === -1) {
            ids.push(tile_id);
            images.push({
                tile_id: tile_id,
                tile_data: renders[i]
            });
        }
        map.push({
            tile_id: tile_id,
            zoom_level: tiles[i][0],
            tile_column: tiles[i][1],
            tile_row: tiles[i][2]
        });
    }

    Step(
        function() {
            var group = this.group();
            for (var i = 0; i < images.length; i++) {
                that.insertImage(images[i], group());
            }
            for (var i = 0; i < map.length; i++) {
                that.insertTile(map[i], group());
            }
        },
        function(err) {
            callback(err);
        }
    );
};

MBTiles.prototype.insertGridKey = function(grid_key, callback) {
    var that = this;
    that.db.prepare(
        'INSERT OR ABORT INTO grid_key ('
        + 'grid_id, '
        + 'key_name) '
        + 'VALUES (?, ?);',
        function(err, statement) {
            if (statement) {
                callback(err);
                statement.bind(1, grid_key.grid_id);
                statement.bind(2, grid_key.key_name);
                statement.step(function(err) {
                    callback(err);
                });
            }
            else {
                callback(err);
            }
        }
    );
};

// Insert a single feature into the grid_data table,
// with the key/axis of `key`
MBTiles.prototype.insertGridData = function(data, key_name, callback) {
    var that = this;
    that.db.prepare(
        'INSERT OR ABORT INTO keymap ('
        + 'key_name, '
        + 'key_json) '
        + 'VALUES (?, ?);',
        function(err, statement) {
            if (statement) {
                callback(err);
                statement.bind(1, data[key_name]);
                statement.bind(2, JSON.stringify(data));
                statement.step(function(err) {
                    callback(err);
                });
            }
            else {
                callback(err);
            }
        }
    );
};

// Insert a single tile into the mbtiles database.
//
// - @param {Object} tile tile object to be inserted.
// - @param {Function} callback
MBTiles.prototype.insertTile = function(tile, callback) {
    var that = this;
    that.db.prepare(
        'INSERT INTO map ('
        + 'tile_id, '
        + 'zoom_level, '
        + 'tile_column, '
        + 'tile_row) '
        + 'VALUES (?, ?, ?, ?);',
        function(err, statement) {
            if (statement) {
                statement.bind(1, tile.tile_id);
                statement.bind(2, tile.zoom_level);
                statement.bind(3, tile.tile_column);
                statement.bind(4, tile.tile_row);
                statement.step(function(err) {
                    callback(err);
                });
            }
            else {
                callback(err);
            }
        }
    );
};

// Insert a single tile into the mbtiles database.
//
// - @param {Object} tile tile object to be inserted.
// - @param {Function} callback
MBTiles.prototype.insertGridTile = function(tile, callback) {
    // TODO: should update instead of inserting
    var that = this;
    that.db.prepare(
        'UPDATE OR REPLACE map SET grid_id = ? WHERE '
        + ' zoom_level = ? AND'
        + ' tile_column = ? AND'
        + ' tile_row = ?;',
        function(err, statement) {
            if (statement) {
                statement.bind(1, tile.grid_id);
                statement.bind(2, tile.zoom_level);
                statement.bind(3, tile.tile_column);
                statement.bind(4, tile.tile_row);
                statement.step(function(err) {
                    callback(err);
                });
            }
            else {
                callback(err);
            }
        }
    );
};

// Insert a single grid into the mbtiles database.
//
// - @param {Object} image object to be inserted.
// - @param {Function} callback
MBTiles.prototype.insertGrid = function(grid, callback) {
    var that = this;
    that.db.prepare(
        'INSERT OR ABORT INTO grid_utfgrid ('
        + 'grid_id, '
        + 'grid_utfgrid) '
        + 'VALUES (?, ?);',
        function(err, statement) {
            if (statement) {
                statement.bind(1, grid.grid_id);
                statement.bind(2, grid.grid_utfgrid);
                statement.step(function(err) {
                    callback(err);
                });
            } else {
                callback(err);
            }
        }
    );
};

// Insert a single image into the mbtiles database.
//
// @param {Object} image object to be inserted.
// @param {Function} callback
MBTiles.prototype.insertImage = function(image, callback) {
    var that = this;
    that.db.prepare(
        'INSERT OR ABORT INTO images ('
        + 'tile_id, '
        + 'tile_data) '
        + 'VALUES (?, ?);',
        function(err, statement) {
            if (statement) {
                statement.bind(1, image.tile_id);
                statement.bind(2, image.tile_data);
                statement.step(function(err) {
                    callback(err);
                });
            } else {
                callback(err);
            }
        }
    );
};

// Insert metadata into the mbtiles database.
//
// - @param {Object} metadata key, value hash of metadata to be inserted.
// - @param {Function} callback
MBTiles.prototype.metadata = function(metadata, callback) {
    var that = this;
    var insert = function(err, key, value, callback) {
        that.db.prepare(
            'INSERT INTO metadata (name, value) VALUES (?, ?);',
            function(err, statement) {
                statement.bind(1, key);
                statement.bind(2, value);
                statement.step(callback);
            }
        );
    };
    Step(
        function() {
            var group = this.group();
            for (var name in metadata) {
                insert(null, name, metadata[name], group());
            }
        },
        function(err) {
            callback(err);
        }
    );
};

// Select a tile from an mbtiles database.
//
// - @param {Number} x tile x coordinate.
// - @param {Number} y tile y coordinate.
// - @param {Number} z tile z coordinate.
// - @param {Function} callback
MBTiles.prototype.tile = function(x, y, z, callback) {
    var that = this;
    Step(
        function() {
            that.db.open(that.filename, this);
        },
        function() {
            var next = this;
            that.db.prepare(
                'SELECT tile_data FROM tiles WHERE '
                + 'zoom_level = ? AND '
                + 'tile_column = ? AND '
                + 'tile_row = ?;',
                function(err, statement) {
                    if (statement) {
                        statement.bind(1, z);
                        statement.bind(2, x);
                        statement.bind(3, y);
                        statement.step(next);
                    }
                    else {
                        next(err);
                    }
                }
            );
        },
        function(err, row) {
            that.db.close();
            if (!err && row && row.tile_data) {
                callback(err, row.tile_data);
            } else {
                callback(err);
            }
        }
    );
};

module.exports = MBTiles;
<|endoftext|>


<commit_before>
new Vue({
    el: '#app',

    // few filters available to change content in main area
    data: {
        platform: 'wordpress',
        hosting: '',
        control: 'max',
        // available options for above data
        options: {
            platform: {
                'wordpress': 'Wordpress',
                'joomla': 'Joomla',
                'magento': 'Magento',
                'shopify': 'Shopify',
                'php': 'Custom PHP',
                '': 'Other / Unknown'
            },
            hosting: {
                'cloudflare': 'Cloudflare',
                'cpanel': 'cPanel / WHM',
                'plesk': 'Plesk',
                'apache': 'Apache',
                'nginx': 'Nginx',
                '': 'Other / Unknown'
            },
            control: {
                'max': 'I have full code access',
                'med': 'I have some code access',
                'min': 'I have access to page content & DNS only'
            }
        },
        showArea: {
            outline: true,
            preMove: true,
            duringMove: true,
            postMove: true,
            aboveAndBeyond: true
        }
    },

    // watch data for changes and update URL
    watch: {
        '$data': {
            handler: function () {
                var url = '?platform=' + this.platform;
                url += '&hosting=' + this.hosting;
                url += '&control=' + this.control;
                history.replaceState({}, '', url);
                window.ga && window.ga('send', 'pageview', url);
            },
            deep: true
        }
    },

    mounted: function () {

        // parse any url params
        var urlParams = {};
        location.search.substring(1).split('&').forEach(function (chunk) {
            var bits = chunk.split('=', 2),
                key = decodeURIComponent(bits[0]).toLowerCase();
            urlParams[key] = decodeURIComponent(bits[1]).toLowerCase();
        });

        // for any url params set, if it's an available option then set it
        if (urlParams) {
            if (urlParams.platform !== undefined && this.options.platform[urlParams.platform]) {
                this.platform = urlParams.platform;
            }
            if (urlParams.hosting !== undefined && this.options.hosting[urlParams.hosting]) {
                this.hosting = urlParams.hosting;
            }
            if (urlParams.control !== undefined && this.options.control[urlParams.control]) {
                this.control = urlParams.control;
            }
        }
    }
});

// report any js errors on page back to analytics
window.addEventListener('error', function(t) {
    if (window.ga) {
        window.ga('send', 'event', 'js error', t.message + ' (' + t.filename + ': ' + t.lineno + ')', t.stack);
    }
});
<commit_msg>Added google analytics tracking to see if anyone actually tries out the add to homepage PWA version<commit_after>
new Vue({
    el: '#app',

    // few filters available to change content in main area
    data: {
        platform: 'wordpress',
        hosting: '',
        control: 'max',
        // available options for above data
        options: {
            platform: {
                'wordpress': 'Wordpress',
                'joomla': 'Joomla',
                'magento': 'Magento',
                'shopify': 'Shopify',
                'php': 'Custom PHP',
                '': 'Other / Unknown'
            },
            hosting: {
                'cloudflare': 'Cloudflare',
                'cpanel': 'cPanel / WHM',
                'plesk': 'Plesk',
                'apache': 'Apache',
                'nginx': 'Nginx',
                '': 'Other / Unknown'
            },
            control: {
                'max': 'I have full code access',
                'med': 'I have some code access',
                'min': 'I have access to page content & DNS only'
            }
        },
        showArea: {
            outline: true,
            preMove: true,
            duringMove: true,
            postMove: true,
            aboveAndBeyond: true
        }
    },

    // watch data for changes and update URL
    watch: {
        '$data': {
            handler: function () {
                var url = '?platform=' + this.platform;
                url += '&hosting=' + this.hosting;
                url += '&control=' + this.control;
                history.replaceState({}, '', url);
                window.ga && window.ga('send', 'pageview', url);
            },
            deep: true
        }
    },

    mounted: function () {

        // parse any url params
        var urlParams = {};
        location.search.substring(1).split('&').forEach(function (chunk) {
            var bits = chunk.split('=', 2),
                key = decodeURIComponent(bits[0]).toLowerCase();
            urlParams[key] = decodeURIComponent(bits[1]).toLowerCase();
        });

        // for any url params set, if it's an available option then set it
        if (urlParams) {
            if (urlParams.platform !== undefined && this.options.platform[urlParams.platform]) {
                this.platform = urlParams.platform;
            }
            if (urlParams.hosting !== undefined && this.options.hosting[urlParams.hosting]) {
                this.hosting = urlParams.hosting;
            }
            if (urlParams.control !== undefined && this.options.control[urlParams.control]) {
                this.control = urlParams.control;
            }
        }
    }
});

// helper to send events to google analytics
// ref: https://developers.google.com/analytics/devguides/collection/analyticsjs/events
var eventTrack = function (category, action, label) {
    if (window.ga) {
        window.ga('send', 'event', category, action, label);
    }
};

// report any js errors on page back to analytics
window.addEventListener('error', function(t) {
    eventTrack('js error', t.message + ' (' + t.filename + ': ' + t.lineno + ')', t.stack);
});

// track if "add to homepage" shown, and if anyone installs it
// ref: https://developers.google.com/web/updates/2015/03/increasing-engagement-with-app-install-banners-in-chrome-for-android#wzxhzdk7did_a_user_install_our_web_appwzxhzdk8
window.addEventListener('beforeinstallprompt', function(e) {
    eventTrack('add to homepage', 'shown');
    e.userChoice.then(function(choiceResult) {
        eventTrack('add to homepage', choiceResult.outcome);
    });
});
<|endoftext|>


<commit_before>var _ = require('./vendor/lodash-custom.js');
var Template = require('../templates/section.html');
var CommentTemplate = require('../templates/comment.html');
var mobileCheck = require('./helpers/mobile-check.js');
var timeago = require('timeago');
var confirm = require('confirmation');
var t = require('t');
var trunkata = require('trunkata');
var classes = require('classes');
var closest = require('closest');
var o = require('dom');

/**
 * Creates a new Section object, which is responsible for managing a
 * single comment section.
 * @param {Object} eventPipe The Emitter object used for passing around events.
 * @param {Array} comments   The array of comments for this section. Optional.
 */
function Section( eventPipe, $el, currentUser, comments, options ) {
  this.eventPipe = eventPipe;
  this.$el = $el;
  this.comments = comments ? comments.comments : [];
  this.currentUser = currentUser || null;
  this.clickEventName = mobileCheck() ? 'touchstart' : 'click';
  this.locale = options.locale || options.trans.locale || 'en';
  this.voting = options.voting || false;

  this.id = $el.data('section-id');

  this.$el.on(this.clickEventName, '.side-comment .marker', _.bind(this.markerClick, this));
  this.$el.on(this.clickEventName, '.side-comment .add-comment', _.bind(this.addCommentClick, this));
  this.$el.on(this.clickEventName, '.side-comment .post', _.bind(this.postCommentClick, this));
  this.$el.on(this.clickEventName, '.side-comment .cancel', _.bind(this.cancelCommentClick, this));
  this.$el.on(this.clickEventName, '.side-comment .delete', _.bind(this.deleteCommentClick, this));
  this.$el.on(this.clickEventName, '.side-comment .upvote', _.bind(this.upvoteCommentClick, this));
  this.$el.on(this.clickEventName, '.side-comment .downvote', _.bind(this.downvoteCommentClick, this));
  this.$el.on(this.clickEventName, '.side-comment .read-more', _.bind(this.readmore, this));
  this.render();
}

/**
 * Click callback event on markers.
 * @param  {Object} event The event object.
 */
Section.prototype.markerClick = function( event ) {
  event.preventDefault();
  this.select();
};

/**
 * Callback for the comment button click event.
 * @param {Object} event The event object.
 */
Section.prototype.addCommentClick = function( event ) {
  event.preventDefault();
  if (this.currentUser) {
    this.showCommentForm();
  } else {
    this.eventPipe.emit('addCommentAttempted');
  }
};

/**
 * Show the comment form for this section.
 */
Section.prototype.showCommentForm = function() {
  if (this.comments.length > 0) {
    this.$el.find('.add-comment').addClass('hide');
    this.$el.find('.comment-form').addClass('active');
  }

  this.focusCommentBox();
};

/**
 * Hides the comment form for this section.
 */
Section.prototype.hideCommentForm = function() {
  if (this.comments.length > 0) {
    this.$el.find('.add-comment').removeClass('hide');
    this.$el.find('.comment-form').removeClass('active');
  }

  this.$el.find('.comment-box').empty();
};

/**
 * Focus on the comment box in the comment form.
 */
Section.prototype.focusCommentBox = function() {
  // NOTE: !!HACK!! Using a timeout here because the autofocus causes a weird
  // "jump" in the form. It renders wider than it should be on screens under 768px
  // and then jumps to a smaller size.
  setTimeout(_.bind(function(){
    this.$el.find('.comment-box').get(0).focus();
  }, this), 300);
};

/**
 * Cancel comment callback.
 * @param  {Object} event The event object.
 */
Section.prototype.cancelCommentClick = function( event ) {
  event.preventDefault();
  this.cancelComment();
};

/**
 * Cancel adding of a comment.
 */
Section.prototype.cancelComment = function() {
  if (this.comments.length > 0) {
    this.hideCommentForm();
  } else {
    this.deselect();
    this.eventPipe.emit('hideComments');
  }
};

/**
 * Post comment callback.
 * @param  {Object} event The event object.
 */
Section.prototype.postCommentClick = function( event ) {
  event.preventDefault();
  this.postComment();
};

/**
 * Post a comment to this section.
 */
Section.prototype.postComment = function() {
  var $commentBox = this.$el.find('.comment-box');
  var commentBody = $commentBox.val();
  if (commentBody.length > 4096) {
    return this.$el.find('.comment-form .error').html(t('Argument is limited to 4096 characters'));
  } else {
    this.$el.find('.comment-form .error').html('');
  }
  var comment = {
    sectionId: this.id,
    comment: commentBody,
    authorAvatarUrl: this.currentUser.avatarUrl,
    authorName: this.currentUser.name,
    authorId: this.currentUser.id,
    authorUrl: this.currentUser.authorUrl || null
  };
  $commentBox.val(''); // Clear the comment.
  this.eventPipe.emit('commentPosted', comment);
};

/**
 * Insert a comment into this sections comment list.
 * @param  {Object} comment A comment object.
 */
Section.prototype.insertComment = function( comment ) {
  this.comments.push(comment);
  var newCommentHtml = _.template(CommentTemplate, {
    comment: comment,
    currentUser: this.currentUser,
    t: t,
    voting: this.voting
  });
  this.$el.find('.comments').append(newCommentHtml);
  this.$el.find('.side-comment').addClass('has-comments');
  this.updateCommentCount();
  this.hideCommentForm();
};

/**
 * Increments the comment count for a given section.
 */
Section.prototype.updateCommentCount = function() {
  this.$el.find('.marker span').text(this.comments.length);
};

/**
 * Event handler for delete comment clicks.
 * @param  {Object} event The event object.
 */
Section.prototype.deleteCommentClick = function( event ) {
  event.preventDefault();
  var commentId = $(event.target).closest('li').data('comment-id');

  function onconfirm(ok) {
    if (ok) return this.deleteComment(commentId);
  }

  confirm(t('side-comments.confirmation.title'), t('side-comments.delete.confirmation'))
  .cancel(t('side-comments.cancel'))
  .ok(t('side-comments.delete'))
  .modal()
  .closable()
  .effect('slide')
  .focus()
  .show(onconfirm.bind(this))
};

/**
 * Finds the comment and emits an event with the comment to be deleted.
 */
Section.prototype.deleteComment = function( commentId ) {
  var comment = _.find(this.comments, { id: commentId });
  comment.sectionId = this.id;
  this.eventPipe.emit('commentDeleted', comment);
};

/**
 * Event handler for upvote comment clicks.
 * @param  {Object} event The event object.
 */
Section.prototype.upvoteCommentClick = function( event ) {
  event.preventDefault();
  var commentId = $(event.target).closest('li').data('comment-id');
  this.upvoteComment(commentId);
};

/**
 * Finds the comment and emits an event with the comment to be upvoted.
 */
Section.prototype.upvoteComment = function( commentId ) {
  var comment = _.find(this.comments, { id: commentId });
  comment.sectionId = this.id;
  this.eventPipe.emit('commentUpvoted', comment);
};

/**
 * Event handler for downvote comment clicks.
 * @param  {Object} event The event object.
 */
Section.prototype.downvoteCommentClick = function( event ) {
  event.preventDefault();
  var commentId = $(event.target).closest('li').data('comment-id');
  this.downvoteComment(commentId);
};

/**
 * Finds the comment and emits an event with the comment to be downvoted.
 */
Section.prototype.downvoteComment = function( commentId ) {
  var comment = _.find(this.comments, { id: commentId });
  comment.sectionId = this.id;
  this.eventPipe.emit('commentDownvoted', comment);
};

/**
 * Removes the comment from the list of comments and the comment array.
 * @param commentId The ID of the comment to be removed from this section.
 */
Section.prototype.removeComment = function( commentId ) {
  this.comments = _.reject(this.comments, { id: commentId });
  this.$el.find('.side-comment .comments li[data-comment-id="'+commentId+'"]').remove();
  this.updateCommentCount();
  if (this.comments.length < 1) {
    this.$el.find('.side-comment').removeClass('has-comments');
  }
};

/**
 * Mark this section as selected. Delsect if this section is already selected.
 */
Section.prototype.select = function() {
  if (this.isSelected()) {
    this.deselect();
    this.eventPipe.emit('sectionDeselected', this);
  } else {
    this.$el.find('.side-comment').addClass('active');

    if (this.comments.length === 0 && this.currentUser) {
      this.focusCommentBox();
    }

    this.$el.find('li[data-comment-id] p.comment').each(function (i, comment) {
      var oldLength = comment.innerHTML.length;
      trunkata(comment, { lines: 4 });
      var newLength = comment.innerHTML.length;
      if (oldLength > newLength) {
        var a = document.createElement('a');
        classes(a).add('action-link').add('read-more');
        a.href = '#';
        a.innerHTML = t('read more');
        comment.appendChild(a);
      }
    });

    this.eventPipe.emit('sectionSelected', this);
  }
};

/**
 * Read full comment
 *
 * @api public
 */

Section.prototype.readmore = function(ev) {
  ev.preventDefault();

  var commentContainer = closest(ev.target,'li[data-comment-id]');
  var id = commentContainer.getAttribute('data-comment-id');
  var comment = get(this.comments, 'id === "%id"'.replace('%id', id));
  var commentText = o('p.comment', commentContainer);

  commentText.html(comment.text);
}

/**
 * Deselect this section.
 */
Section.prototype.deselect = function() {
  this.$el.find('.side-comment').removeClass('active');
  this.hideCommentForm();
};

Section.prototype.isSelected = function() {
  return this.$el.find('.side-comment').hasClass('active');
};

/**
 * Get the class to be used on the side comment section wrapper.
 * @return {String} The class names to use.
 */
Section.prototype.sectionClasses = function() {
  var classes = '';

  if (this.comments.length > 0) {
    classes = classes + ' has-comments';
  }
  if (!this.currentUser) {
    classes = classes + ' no-current-user'
  }

  return classes;
};

/**
 * Render this section into the DOM.
 */
Section.prototype.render = function() {
  this.$el.find('.side-comment').remove();
  var el = $(_.template(Template, {
    commentTemplate: CommentTemplate,
    comments: this.comments,
    sectionClasses: this.sectionClasses(),
    currentUser: this.currentUser,
    t: t,
    voting: this.voting
  }))
  el.appendTo(this.$el);
  timeago('.ago', { lang: this.locale, interval: 10000 });
};

/**
 * Desttroy this Section object. Generally meaning unbind events.
 */
Section.prototype.destroy = function() {
  this.$el.off();
}

module.exports = Section;

function get(list, query) {
  var match;
  var test = new Function('_', 'return _.' + query);

  list.some(function(l) {
    if (test(l)) {
      match = l;
      return true;
    };
    return false;
  });
  return match || null;
}<commit_msg>Made timeago instance singleton #2<commit_after>var _ = require('./vendor/lodash-custom.js');
var Template = require('../templates/section.html');
var CommentTemplate = require('../templates/comment.html');
var mobileCheck = require('./helpers/mobile-check.js');
var timeago = require('timeago');
var confirm = require('confirmation');
var t = require('t');
var trunkata = require('trunkata');
var classes = require('classes');
var closest = require('closest');
var o = require('dom');

var time = timeago('.ago', { lang: this.locale, interval: 10000 });

/**
 * Creates a new Section object, which is responsible for managing a
 * single comment section.
 * @param {Object} eventPipe The Emitter object used for passing around events.
 * @param {Array} comments   The array of comments for this section. Optional.
 */
function Section( eventPipe, $el, currentUser, comments, options ) {
  this.eventPipe = eventPipe;
  this.$el = $el;
  this.comments = comments ? comments.comments : [];
  this.currentUser = currentUser || null;
  this.clickEventName = mobileCheck() ? 'touchstart' : 'click';
  this.locale = options.locale || options.trans.locale || 'en';
  this.voting = options.voting || false;

  this.id = $el.data('section-id');

  this.$el.on(this.clickEventName, '.side-comment .marker', _.bind(this.markerClick, this));
  this.$el.on(this.clickEventName, '.side-comment .add-comment', _.bind(this.addCommentClick, this));
  this.$el.on(this.clickEventName, '.side-comment .post', _.bind(this.postCommentClick, this));
  this.$el.on(this.clickEventName, '.side-comment .cancel', _.bind(this.cancelCommentClick, this));
  this.$el.on(this.clickEventName, '.side-comment .delete', _.bind(this.deleteCommentClick, this));
  this.$el.on(this.clickEventName, '.side-comment .upvote', _.bind(this.upvoteCommentClick, this));
  this.$el.on(this.clickEventName, '.side-comment .downvote', _.bind(this.downvoteCommentClick, this));
  this.$el.on(this.clickEventName, '.side-comment .read-more', _.bind(this.readmore, this));
  this.render();
}

/**
 * Click callback event on markers.
 * @param  {Object} event The event object.
 */
Section.prototype.markerClick = function( event ) {
  event.preventDefault();
  this.select();
};

/**
 * Callback for the comment button click event.
 * @param {Object} event The event object.
 */
Section.prototype.addCommentClick = function( event ) {
  event.preventDefault();
  if (this.currentUser) {
    this.showCommentForm();
  } else {
    this.eventPipe.emit('addCommentAttempted');
  }
};

/**
 * Show the comment form for this section.
 */
Section.prototype.showCommentForm = function() {
  if (this.comments.length > 0) {
    this.$el.find('.add-comment').addClass('hide');
    this.$el.find('.comment-form').addClass('active');
  }

  this.focusCommentBox();
};

/**
 * Hides the comment form for this section.
 */
Section.prototype.hideCommentForm = function() {
  if (this.comments.length > 0) {
    this.$el.find('.add-comment').removeClass('hide');
    this.$el.find('.comment-form').removeClass('active');
  }

  this.$el.find('.comment-box').empty();
};

/**
 * Focus on the comment box in the comment form.
 */
Section.prototype.focusCommentBox = function() {
  // NOTE: !!HACK!! Using a timeout here because the autofocus causes a weird
  // "jump" in the form. It renders wider than it should be on screens under 768px
  // and then jumps to a smaller size.
  setTimeout(_.bind(function(){
    this.$el.find('.comment-box').get(0).focus();
  }, this), 300);
};

/**
 * Cancel comment callback.
 * @param  {Object} event The event object.
 */
Section.prototype.cancelCommentClick = function( event ) {
  event.preventDefault();
  this.cancelComment();
};

/**
 * Cancel adding of a comment.
 */
Section.prototype.cancelComment = function() {
  if (this.comments.length > 0) {
    this.hideCommentForm();
  } else {
    this.deselect();
    this.eventPipe.emit('hideComments');
  }
};

/**
 * Post comment callback.
 * @param  {Object} event The event object.
 */
Section.prototype.postCommentClick = function( event ) {
  event.preventDefault();
  this.postComment();
};

/**
 * Post a comment to this section.
 */
Section.prototype.postComment = function() {
  var $commentBox = this.$el.find('.comment-box');
  var commentBody = $commentBox.val();
  if (commentBody.length > 4096) {
    return this.$el.find('.comment-form .error').html(t('Argument is limited to 4096 characters'));
  } else {
    this.$el.find('.comment-form .error').html('');
  }
  var comment = {
    sectionId: this.id,
    comment: commentBody,
    authorAvatarUrl: this.currentUser.avatarUrl,
    authorName: this.currentUser.name,
    authorId: this.currentUser.id,
    authorUrl: this.currentUser.authorUrl || null
  };
  $commentBox.val(''); // Clear the comment.
  this.eventPipe.emit('commentPosted', comment);
};

/**
 * Insert a comment into this sections comment list.
 * @param  {Object} comment A comment object.
 */
Section.prototype.insertComment = function( comment ) {
  this.comments.push(comment);
  var newCommentHtml = _.template(CommentTemplate, {
    comment: comment,
    currentUser: this.currentUser,
    t: t,
    voting: this.voting
  });
  this.$el.find('.comments').append(newCommentHtml);
  this.$el.find('.side-comment').addClass('has-comments');
  this.updateCommentCount();
  this.hideCommentForm();
  time.update();
};

/**
 * Increments the comment count for a given section.
 */
Section.prototype.updateCommentCount = function() {
  this.$el.find('.marker span').text(this.comments.length);
};

/**
 * Event handler for delete comment clicks.
 * @param  {Object} event The event object.
 */
Section.prototype.deleteCommentClick = function( event ) {
  event.preventDefault();
  var commentId = $(event.target).closest('li').data('comment-id');

  function onconfirm(ok) {
    if (ok) return this.deleteComment(commentId);
  }

  confirm(t('side-comments.confirmation.title'), t('side-comments.delete.confirmation'))
  .cancel(t('side-comments.cancel'))
  .ok(t('side-comments.delete'))
  .modal()
  .closable()
  .effect('slide')
  .focus()
  .show(onconfirm.bind(this))
};

/**
 * Finds the comment and emits an event with the comment to be deleted.
 */
Section.prototype.deleteComment = function( commentId ) {
  var comment = _.find(this.comments, { id: commentId });
  comment.sectionId = this.id;
  this.eventPipe.emit('commentDeleted', comment);
};

/**
 * Event handler for upvote comment clicks.
 * @param  {Object} event The event object.
 */
Section.prototype.upvoteCommentClick = function( event ) {
  event.preventDefault();
  var commentId = $(event.target).closest('li').data('comment-id');
  this.upvoteComment(commentId);
};

/**
 * Finds the comment and emits an event with the comment to be upvoted.
 */
Section.prototype.upvoteComment = function( commentId ) {
  var comment = _.find(this.comments, { id: commentId });
  comment.sectionId = this.id;
  this.eventPipe.emit('commentUpvoted', comment);
};

/**
 * Event handler for downvote comment clicks.
 * @param  {Object} event The event object.
 */
Section.prototype.downvoteCommentClick = function( event ) {
  event.preventDefault();
  var commentId = $(event.target).closest('li').data('comment-id');
  this.downvoteComment(commentId);
};

/**
 * Finds the comment and emits an event with the comment to be downvoted.
 */
Section.prototype.downvoteComment = function( commentId ) {
  var comment = _.find(this.comments, { id: commentId });
  comment.sectionId = this.id;
  this.eventPipe.emit('commentDownvoted', comment);
};

/**
 * Removes the comment from the list of comments and the comment array.
 * @param commentId The ID of the comment to be removed from this section.
 */
Section.prototype.removeComment = function( commentId ) {
  this.comments = _.reject(this.comments, { id: commentId });
  this.$el.find('.side-comment .comments li[data-comment-id="'+commentId+'"]').remove();
  this.updateCommentCount();
  if (this.comments.length < 1) {
    this.$el.find('.side-comment').removeClass('has-comments');
  }
};

/**
 * Mark this section as selected. Delsect if this section is already selected.
 */
Section.prototype.select = function() {
  if (this.isSelected()) {
    this.deselect();
    this.eventPipe.emit('sectionDeselected', this);
  } else {
    this.$el.find('.side-comment').addClass('active');

    if (this.comments.length === 0 && this.currentUser) {
      this.focusCommentBox();
    }

    this.$el.find('li[data-comment-id] p.comment').each(function (i, comment) {
      var oldLength = comment.innerHTML.length;
      trunkata(comment, { lines: 4 });
      var newLength = comment.innerHTML.length;
      if (oldLength > newLength) {
        var a = document.createElement('a');
        classes(a).add('action-link').add('read-more');
        a.href = '#';
        a.innerHTML = t('read more');
        comment.appendChild(a);
      }
    });

    this.eventPipe.emit('sectionSelected', this);
  }
};

/**
 * Read full comment
 *
 * @api public
 */

Section.prototype.readmore = function(ev) {
  ev.preventDefault();

  var commentContainer = closest(ev.target,'li[data-comment-id]');
  var id = commentContainer.getAttribute('data-comment-id');
  var comment = get(this.comments, 'id === "%id"'.replace('%id', id));
  var commentText = o('p.comment', commentContainer);

  commentText.html(comment.text);
}

/**
 * Deselect this section.
 */
Section.prototype.deselect = function() {
  this.$el.find('.side-comment').removeClass('active');
  this.hideCommentForm();
};

Section.prototype.isSelected = function() {
  return this.$el.find('.side-comment').hasClass('active');
};

/**
 * Get the class to be used on the side comment section wrapper.
 * @return {String} The class names to use.
 */
Section.prototype.sectionClasses = function() {
  var classes = '';

  if (this.comments.length > 0) {
    classes = classes + ' has-comments';
  }
  if (!this.currentUser) {
    classes = classes + ' no-current-user'
  }

  return classes;
};

/**
 * Render this section into the DOM.
 */
Section.prototype.render = function() {
  this.$el.find('.side-comment').remove();
  var el = $(_.template(Template, {
    commentTemplate: CommentTemplate,
    comments: this.comments,
    sectionClasses: this.sectionClasses(),
    currentUser: this.currentUser,
    t: t,
    voting: this.voting
  }))
  el.appendTo(this.$el);
  time.update();
};

/**
 * Desttroy this Section object. Generally meaning unbind events.
 */
Section.prototype.destroy = function() {
  this.$el.off();
}

module.exports = Section;

function get(list, query) {
  var match;
  var test = new Function('_', 'return _.' + query);

  list.some(function(l) {
    if (test(l)) {
      match = l;
      return true;
    };
    return false;
  });
  return match || null;
}<|endoftext|>


<commit_before>var loadVine = function() {
  var v = $.ajax({
    type: 'GET',
    url: 'http://dev.univfeed.com/api/schools',
    dataType: 'jsonp',
    success: function(data){
      return data;
    },
    error: function(xhr, type){
      alert('Ajax error!')
    }
  });
  return v;
}
<commit_msg>Update app.js<commit_after>var loadVine = function() {
  var v = $.ajax({
    type: 'GET',
    url: 'http://dev.univfeed.com/api/schools',
    dataType: 'jsonp',
    success: function(data){
      console.log(JSON.stringify(data));
      return data;
    },
    error: function(xhr, type){
      alert('Ajax error!')
    }
  });
  return v;
}
<|endoftext|>


<commit_before>
/**
 * Module dependencies
 */

var XHR = require('./polling-xhr')
  , JSONP = require('./polling-jsonp')
  , websocket = require('./websocket')
  , flashsocket = require('./flashsocket')
  , util = require('../util');

/**
 * Export transports.
 */

exports.polling = polling;
exports.websocket = websocket;
exports.flashsocket = flashsocket;

/**
 * Global reference.
 */

var global = require('global')();

/**
 * Polling transport polymorphic constructor.
 * Decides on xhr vs jsonp based on feature detection.
 *
 * @api private
 */

function polling (opts) {
  var xhr
    , xd = false
    , isXProtocol = false;

  if (global.location) {
    var isSSL = 'https:' == location.protocol;
    var port = location.port;

    // some user agents have empty `location.port`
    if (Number(port) !== port) {
      port = isSSL ? 443 : 80;
    }

    xd = opts.hostname != location.hostname || port != opts.port;
    isXProtocol = opts.secure != isSSL;
  }

  xhr = util.request(xd);
  /* See #7 at http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx */
  if (isXProtocol && global.XDomainRequest && xhr instanceof global.XDomainRequest) {
    return new JSONP(opts);
  }

  if (xhr && !opts.forceJSONP) {
    return new XHR(opts);
  } else {
    return new JSONP(opts);
  }
};
<commit_msg>transports: pass the options to the `util.request()` function<commit_after>
/**
 * Module dependencies
 */

var XHR = require('./polling-xhr')
  , JSONP = require('./polling-jsonp')
  , websocket = require('./websocket')
  , flashsocket = require('./flashsocket')
  , util = require('../util');

/**
 * Export transports.
 */

exports.polling = polling;
exports.websocket = websocket;
exports.flashsocket = flashsocket;

/**
 * Global reference.
 */

var global = require('global')();

/**
 * Polling transport polymorphic constructor.
 * Decides on xhr vs jsonp based on feature detection.
 *
 * @api private
 */

function polling (opts) {
  var xhr
    , xd = false
    , isXProtocol = false;

  if (global.location) {
    var isSSL = 'https:' == location.protocol;
    var port = location.port;

    // some user agents have empty `location.port`
    if (Number(port) !== port) {
      port = isSSL ? 443 : 80;
    }

    xd = opts.hostname != location.hostname || port != opts.port;
    isXProtocol = opts.secure != isSSL;
  }

  xhr = util.request(xd, opts);
  /* See #7 at http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx */
  if (isXProtocol && global.XDomainRequest && xhr instanceof global.XDomainRequest) {
    return new JSONP(opts);
  }

  if (xhr && !opts.forceJSONP) {
    return new XHR(opts);
  } else {
    return new JSONP(opts);
  }
};
<|endoftext|>


<commit_before>// Copyright 2018, University of Colorado Boulder

/**
 * Return an object of data about the simulation and the browser
 * much of the code was copied from SimTroubleshootPage.html in the website repo
 *
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */

define( require => {
  'use strict';

  // modules
  const joist = require( 'JOIST/joist' );
  const Util = require( 'SCENERY/util/Util' );

  // ifphetio
  const dataStream = require( 'ifphetio!PHET_IO/dataStream' );
  const phetioCommandProcessor = require( 'ifphetio!PHET_IO/phetioCommandProcessor' );

  // constants
  const info = {};

  function putInfo( key, value ) {
    if ( value === undefined ) {
      value = '{{undefined}}';
    }
    info[ key ] = value;
  }

  // globals
  putInfo( 'url', window.location.href );
  putInfo( 'randomSeed', window.phet.chipper.randomSeed );
  putInfo( 'userAgent', window.navigator.userAgent );
  putInfo( 'language', window.navigator.language );
  putInfo( 'window', window.innerWidth + 'x' + window.innerHeight );
  putInfo( 'referrer', document.referrer );

  // from Scenery Util
  putInfo( 'checkIE11StencilSupport', Util.checkIE11StencilSupport );
  if ( phet.chipper.queryParameters.webgl ) {
    putInfo( 'isWebGLSupported', Util.isWebGLSupported );
  }

  let canvas;
  let context;
  let backingStorePixelRatio;

  try {
    canvas = document.createElement( 'canvas' );
    context = canvas.getContext( '2d' );
    backingStorePixelRatio = Util.backingStorePixelRatio( context );
  }
  catch( e ) {} // eslint-disable-line

  putInfo( 'pixelRatio', ( window.devicePixelRatio || 1 ) + '/' + backingStorePixelRatio );

  const flags = [];
  if ( window.navigator.pointerEnabled ) { flags.push( 'pointerEnabled' ); }
  if ( window.navigator.msPointerEnabled ) { flags.push( 'msPointerEnabled' ); }
  if ( !window.navigator.onLine ) { flags.push( 'offline' ); }
  if ( ( window.devicePixelRatio || 1 ) / backingStorePixelRatio !== 1 ) { flags.push( 'pixelRatioScaling' ); }
  putInfo( 'flags', flags.join( ', ' ) );

  canvas = null; // dispose only reference

  const simInfo = {
    getInfo( sim, packageJSON ) {

      if ( !info.simName ) {
        putInfo( 'simName', sim.name );
        putInfo( 'simVersion', sim.version );
        putInfo( 'repoName', packageJSON.name );
      }

      // (phet-io) if there is metadata from the wrapper
      if ( phet.phetio ) {
        putInfo( 'wrapperMetadata', phet.phetio.simStartedMetadata );
        putInfo( 'dataStreamVersion', dataStream.VERSION );
        putInfo( 'phetioCommandProcessorVersion', phetioCommandProcessor.VERSION );
      }

      return info;
    }
  };

  return joist.register( 'simInfo', simInfo );
} );
<commit_msg>Fixed check for stencil support, see https://github.com/phetsims/phet-io/issues/1398<commit_after>// Copyright 2018, University of Colorado Boulder

/**
 * Return an object of data about the simulation and the browser
 * much of the code was copied from SimTroubleshootPage.html in the website repo
 *
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */

define( require => {
  'use strict';

  // modules
  const joist = require( 'JOIST/joist' );
  const Util = require( 'SCENERY/util/Util' );

  // ifphetio
  const dataStream = require( 'ifphetio!PHET_IO/dataStream' );
  const phetioCommandProcessor = require( 'ifphetio!PHET_IO/phetioCommandProcessor' );

  // constants
  const info = {};

  function putInfo( key, value ) {
    if ( value === undefined ) {
      value = '{{undefined}}';
    }
    info[ key ] = value;
  }

  // globals
  putInfo( 'url', window.location.href );
  putInfo( 'randomSeed', window.phet.chipper.randomSeed );
  putInfo( 'userAgent', window.navigator.userAgent );
  putInfo( 'language', window.navigator.language );
  putInfo( 'window', window.innerWidth + 'x' + window.innerHeight );
  putInfo( 'referrer', document.referrer );

  // from Scenery Util
  putInfo( 'checkIE11StencilSupport', Util.checkIE11StencilSupport() );
  if ( phet.chipper.queryParameters.webgl ) {
    putInfo( 'isWebGLSupported', Util.isWebGLSupported );
  }

  let canvas;
  let context;
  let backingStorePixelRatio;

  try {
    canvas = document.createElement( 'canvas' );
    context = canvas.getContext( '2d' );
    backingStorePixelRatio = Util.backingStorePixelRatio( context );
  }
  catch( e ) {} // eslint-disable-line

  putInfo( 'pixelRatio', ( window.devicePixelRatio || 1 ) + '/' + backingStorePixelRatio );

  const flags = [];
  if ( window.navigator.pointerEnabled ) { flags.push( 'pointerEnabled' ); }
  if ( window.navigator.msPointerEnabled ) { flags.push( 'msPointerEnabled' ); }
  if ( !window.navigator.onLine ) { flags.push( 'offline' ); }
  if ( ( window.devicePixelRatio || 1 ) / backingStorePixelRatio !== 1 ) { flags.push( 'pixelRatioScaling' ); }
  putInfo( 'flags', flags.join( ', ' ) );

  canvas = null; // dispose only reference

  const simInfo = {
    getInfo( sim, packageJSON ) {

      if ( !info.simName ) {
        putInfo( 'simName', sim.name );
        putInfo( 'simVersion', sim.version );
        putInfo( 'repoName', packageJSON.name );
      }

      // (phet-io) if there is metadata from the wrapper
      if ( phet.phetio ) {
        putInfo( 'wrapperMetadata', phet.phetio.simStartedMetadata );
        putInfo( 'dataStreamVersion', dataStream.VERSION );
        putInfo( 'phetioCommandProcessorVersion', phetioCommandProcessor.VERSION );
      }

      return info;
    }
  };

  return joist.register( 'simInfo', simInfo );
} );
<|endoftext|>


<commit_before>

/* ----------------------- MODEL ----------------------- */

//Constructor function for pointOfInterest object (i.e. locations in Hampden)

var Location = function (data) {

    var self = this;

    this.name = data.name;
    //this.imgSrc = ko.observable(data.imgSrc);
    this.city = "Baltimore";
    this.state = "Maryland";
    this.zipcode = 21211;
    this.streetNumber = data.streetNumber;
    this.street = data.street;
    this.categories = data.categories;  //categories is an array
    this.display = ko.observable(true);
    this.latlng = '';
    this.placeName = '';

    self.address = function () {
        return self.streetNumber + " " + self.street + " "
        + self.city + ", " + self.state + " " + self.zipcode;
    }

};

var locations = [

 {
          name: "Fraziers",
          streetNumber: "1400",
          street: "W. 36th",
          categories: ["bar", "restaurant"],
          latLng : {}

  },

 {
          name: "Milagros",
          streetNumber: "1005",
          street: "W. 36th",
          categories: ["shop"] ,
          latLng : {}

  },

   {
          name: "Milagros Neighbor",
          streetNumber: "1009",
          street: "W. 36th",
          categories: ["shop"] ,
          latLng : {}

  },

   {
          name: "Charm City Headshots",
          streetNumber: "3646",
          street: "Elm Avenue",
          categories: ["photographer"] ,
          latLng : {}

  },
]

/* ----------------------- VIEW MODEL ----------------------- */

function AppViewModel() {

    var self = this;

    this.searchBox =  ko.observable("Enter search text");

    //List of all locations in the model
    self.locationObjList = ko.observableArray([]);

    //Iterate through locations array, creating new location objects and
    //adding them to the locationObjList observable array

    locations.forEach(function(locationObj) {
        self.locationObjList.push(new Location(locationObj));
    });

    //List of addresses only for use in the calculating Google map markers
    self.addressList = ko.observableArray([]);

    //Iterate through locationList, adding addresses to address list
    self.locationObjList().forEach(function(locationObj) {
        self.addressList.push(locationObj.address());
    });

    self.displayLocation = ko.computed(function() {
      for (i=0; i<self.locationObjList().length;i++){
        //if searchBox text is a match for part of the name, set display == true on location object
        if (self.locationObjList()[i].name.search(self.searchBox()) !== -1) {
          self.locationObjList()[i].display(true);
        }
        //else set display = false
        else {
          self.locationObjList()[i].display(false);
        }
      }
    });

    //set up data for google map object defined below
    var mapOptions = {
    center: { lat: 39.331280, lng: -76.631524},
    zoom: 16
    };

    //define new google map object
    var map = new google.maps.Map(document.getElementById('hampdenMap'), mapOptions);

    //Iterate through locationObjList (array of all location objects), calculate latlng and placeNames based on their
    //addresses, and create map markers -- adding all of these as properties to the object

    function mapMaker() {
      for (var x = 0; x < self.locationObjList().length; x++) {

      $.getJSON('http://maps.googleapis.com/maps/api/geocode/json?address='+self.locationObjList()[x].address()+
            '&sensor=false', null, function (data) {
              var p = data.results[0].geometry.location;
              var newLatLng = new google.maps.LatLng(p.lat, p.lng);

              //this line prints '4' every time, not 0,1,2,3 as i'd expect
              console.log(x);

              //line below throws an error
             // self.locationObjList()[x].latlng(newLatng);

              var marker =
                new google.maps.Marker({
                    animation: google.maps.Animation.DROP,
                    position: newLatLng,
                    map: map,

                });

            });

          };

      };
       mapMaker();

    };

// Activates knockout.js
ko.applyBindings(new AppViewModel());

/// ...

/* ----------------------- GOOGLE MAP MAKER ----------------------- */

var addresses = ['3646 Elm Ave, Baltimore, MD', '3400 Falls Road, Baltimore, MD',
   '1005 W. 36th Street, Baltimore, MD'];

// mapMaker();
      // $(document).ready(function() {
      //       mapMaker();
      //   })

<commit_msg>Attempt to deal with closure issue in mapmaker function<commit_after>

/* ----------------------- MODEL ----------------------- */

//Constructor function for pointOfInterest object (i.e. locations in Hampden)

var Location = function (data) {

    var self = this;

    this.name = data.name;
    //this.imgSrc = ko.observable(data.imgSrc);
    this.city = "Baltimore";
    this.state = "Maryland";
    this.zipcode = 21211;
    this.streetNumber = data.streetNumber;
    this.street = data.street;
    this.categories = data.categories;  //categories is an array
    this.display = ko.observable(true);
    this.placeName = '';
    this.latLng = {};
    this.marker = {};

    self.address = function () {
        return self.streetNumber + " " + self.street + " "
        + self.city + ", " + self.state + " " + self.zipcode;
    }

};

var locations = [

 {
          name: "Fraziers",
          streetNumber: "1400",
          street: "W. 36th",
          categories: ["bar", "restaurant"]

  },

 {
          name: "Milagros",
          streetNumber: "1005",
          street: "W. 36th",
          categories: ["shop"]

  },

   {
          name: "Milagros Neighbor",
          streetNumber: "1009",
          street: "W. 36th",
          categories: ["shop"]

  },

   {
          name: "Charm City Headshots",
          streetNumber: "3646",
          street: "Elm Avenue",
          categories: ["photographer"]

  },
]

/* ----------------------- VIEW MODEL ----------------------- */

function AppViewModel() {

    var self = this;

    this.searchBox =  ko.observable("Enter search text");

    //List of all locations in the model
    self.locationObjList = ko.observableArray([]);

    //Iterate through locations array, creating new location objects and
    //adding them to the locationObjList observable array

    locations.forEach(function(locationObj) {
        self.locationObjList.push(new Location(locationObj));
    });

    //List of addresses only for use in the calculating Google map markers
    self.addressList = ko.observableArray([]);

    //Iterate through locationList, adding addresses to address list
    self.locationObjList().forEach(function(locationObj) {
        self.addressList.push(locationObj.address());
    });

    self.displayLocation = ko.computed(function() {
      for (i=0; i<self.locationObjList().length;i++){
        //if searchBox text is a match for part of the name, set display == true on location object
        if (self.locationObjList()[i].name.search(self.searchBox()) !== -1) {
          self.locationObjList()[i].display(true);
        }
        //else set display = false
        else {
          self.locationObjList()[i].display(false);
        }
      }
    });

    //set up data for google map object defined below
    var mapOptions = {
    center: { lat: 39.331280, lng: -76.631524},
    zoom: 16
    };

    //define new google map object
    var map = new google.maps.Map(document.getElementById('hampdenMap'), mapOptions);

    function setLatLng (locationObj, latLng, marker, x) {
        console.log(locationObj, latLng, marker, x);
        locationObj.latLng = latLng;
        locationObj.marker = marker;
    }

    function setLatLngFactory (locationObj, latLng, marker, x) {
      return setLatLng(locationObj, latLng, marker);
    };

    //Iterate through locationObjList (array of all location objects), calculate latlng and placeNames based on their
    //addresses, and create map markers -- adding all of these as properties to the object
    function mapMaker() {
      for (var x = 0; x < self.locationObjList().length; x++) {

      $.getJSON('http://maps.googleapis.com/maps/api/geocode/json?address='+self.locationObjList()[x].address()+
            '&sensor=false', null, function (data) {
              var p = data.results[0].geometry.location;
              var latLng = new google.maps.LatLng(p.lat, p.lng);
              var marker =
                new google.maps.Marker({
                    animation: google.maps.Animation.DROP,
                    position: latLng,
                    map: map,
                });
              //function factory. lock value of self.locationObjList()[x] into a closure
              setLatLngFactory(self.locationObjList()[x],latLng, marker, x);
            });

          };

      };
       mapMaker();
       console.log("LOL", self.locationObjList());

    };

// Activates knockout.js
ko.applyBindings(new AppViewModel());

/// ...

/* ----------------------- GOOGLE MAP MAKER ----------------------- */

var addresses = ['3646 Elm Ave, Baltimore, MD', '3400 Falls Road, Baltimore, MD',
   '1005 W. 36th Street, Baltimore, MD'];

// mapMaker();
      // $(document).ready(function() {
      //       mapMaker();
      //   })

<|endoftext|>


<commit_before>var fuzzyMode = (function() {
  var fuzzyBox = null;  // the dialog instance for this window
  var completers = {};  // completer cache

  function createCompleter(name) {
    if (name === 'smart')
      return new completion.SmartCompleter({
        'wiki ': [ 'Wikipedia (en)', 'http://en.wikipedia.org/wiki/%s' ],
        'luck ': [ 'Google Lucky (en)', 'http://www.google.com/search?q=%s&btnI=I%27m+Feeling+Lucky' ],
        'cc '  : [ 'dict.cc',        'http://www.dict.cc/?s=%s' ],
        ';'    : [ 'goto',           '%s' ],
        '?'    : [ 'search',         function(query) { return utils.createSearchUrl(query) } ],
        });
    else if (name === 'history')
      return new completion.FuzzyHistoryCompleter(1500);
    else if (name === 'bookmarks')
      return new completion.FuzzyBookmarkCompleter();
    else if (name === 'tabs')
      return new completion.FuzzyTabCompleter();
    else if (name === 'tabsSorted')
      return new completion.MergingCompleter([getCompleter('tabs')]);
    else if (name === 'all')
      return new completion.MergingCompleter([
        getCompleter('smart'),
        getCompleter('bookmarks'),
        getCompleter('history'),
        getCompleter('tabs'),
        ]);
  }
  function getCompleter(name) {
    if (!(name in completers))
      completers[name] = createCompleter(name);
    return completers[name];
  }

  /** Trigger the fuzzy mode dialog */
  function start(name, reverseAction) {
    var completer = getCompleter(name);
    if (!fuzzyBox)
      fuzzyBox = new FuzzyBox(10, 300);
    completer.refresh();
    fuzzyBox.setCompleter(completer);
    fuzzyBox.show(reverseAction);
  }

  /** User interface for fuzzy completion */
  var FuzzyBox = function(maxResults, refreshInterval) {
    this.prompt = '>';
    this.maxResults = maxResults;
    this.refreshInterval = refreshInterval;
    this.initDom();
    this.reset();
  }
  FuzzyBox.prototype = {
    setCompleter: function(completer) {
      this.completer = completer;
      this.reset();
    },

    show: function(reverseAction) {
      this.reverseAction = reverseAction;
      this.box.style.display = 'block';
      var self = this;
      handlerStack.push({ keydown: function(event) { self.onKeydown(event); }});
    },

    hide: function() {
      this.box.style.display = 'none';
      handlerStack.pop();
    },

    reset: function() {
      this.query = '';
      // query used to filter the last completion result. We need this for asynchronous updating
      this.lastQuery = null;
      this.completions = [];
      this.selection = 0;
      // force synchronous updating so that the old results will not be flash up shortly
      this.update(true);
    },

    updateSelection: function() {
      this.completionList.childNodes.forEach(function(child, i) {
        child.className = (i == this.selection) ? 'selected' : '';
      });
    },

    onKeydown: function(event) {
      var keyChar = getKeyChar(event);

      if (isEscape(event)) {
        this.hide();
      }

      // move selection with Up/Down, Tab/Shift-Tab, Ctrl-k/Ctrl-j
      else if (keyChar === 'up' || (event.keyCode == 9 && event.shiftKey)
              || (keyChar === 'k' && event.ctrlKey)) {
        if (this.selection > 0)
          this.selection -= 1;
        this.updateSelection();
      }
      else if (keyChar === 'down' || (event.keyCode == 9 && !event.shiftKey)
              || (keyChar === 'j' && isPrimaryModifierKey(event))) {
        if (this.selection < this.completions.length - 1)
          this.selection += 1;
        this.updateSelection();
      }

      else if (event.keyCode == keyCodes.backspace) {
        if (this.query.length > 0) {
          this.query = this.query.substr(0, this.query.length-1);
          this.update();
        }
      }

      // refresh with F5
      else if (keyChar == 'f5') {
        this.completer.refresh();
        this.update();
      }

      // use primary action with Enter. Holding down Shift/Ctrl uses the alternative action
      // (opening in new tab)
      else if (event.keyCode == keyCodes.enter) {
        this.update(true); // force synchronous update

        var alternative = (event.shiftKey || isPrimaryModifierKey(event));
        if (this.reverseAction)
          alternative = !alternative;
        this.completions[this.selection].action[alternative ? 1 : 0]();
        this.hide();
        this.reset();
      }

      else if (keyChar.length == 1) {
        this.query += keyChar;
        this.update();
      }

      event.stopPropagation();
      event.preventDefault();
      return true;
    },

    updateInput: function() {
      this.query = this.query.replace(/^\s*/, '');
      this.input.textContent = this.query;
    },

    updateCompletions: function() {
      if (this.query.length == 0) {
        this.completionList.style.display = 'none';
        return;
      }

      var self = this;
      this.completer.filter(this.query, function(completions) {
        self.completions = completions.slice(0, self.maxResults);

        // clear completions
        self.completionList.innerHTML = self.completions.map(function(completion) {
          return '
' + completion.render() + '
';
        }).join('');

        self.completionList.style.display = self.completions.length > 0 ? 'block' : 'none';
        self.updateSelection();
      });
    },

    update: function(sync) {
      sync = sync || false; // explicitely default to asynchronous updating
      this.updateInput();

      if (sync) {
        this.updateCompletions();
      } else {
        var self = this;
        // always update asynchronously for better user experience and to take some load off the CPU
        // (not every keystroke will cause a dedicated update)
        setTimeout(function() {
          if (self.query === self.lastQuery)
            return;
          self.lastQuery = self.query;
          self.updateCompletions();
        }, this.refreshInterval);
      }
    },

    initDom: function() {
      this.box = utils.createElementFromHtml(
        '
'+
          '
'+
            '' + utils.escapeHtml(this.prompt) + ' '+
            '
'+
          '

');
      this.box.style.display = 'none';
      document.body.appendChild(this.box);

      this.input          = document.getElementById("fuzzyboxInput");
      this.completionList = document.getElementById("fuzzyboxCompletions");
    },
  }

  // public interface
  return {
    activateAll:       function() { start('all',        false); },
    activateAllNewTab: function() { start('all',        true);  },
    activateTabs:      function() { start('tabsSorted', false); },
  }

})();
<commit_msg>fix regression in 10a77d25c3<commit_after>var fuzzyMode = (function() {
  var fuzzyBox = null;  // the dialog instance for this window
  var completers = {};  // completer cache

  function createCompleter(name) {
    if (name === 'smart')
      return new completion.SmartCompleter({
        'wiki ': [ 'Wikipedia (en)', 'http://en.wikipedia.org/wiki/%s' ],
        'luck ': [ 'Google Lucky (en)', 'http://www.google.com/search?q=%s&btnI=I%27m+Feeling+Lucky' ],
        'cc '  : [ 'dict.cc',        'http://www.dict.cc/?s=%s' ],
        ';'    : [ 'goto',           '%s' ],
        '?'    : [ 'search',         function(query) { return utils.createSearchUrl(query) } ],
        });
    else if (name === 'history')
      return new completion.FuzzyHistoryCompleter(1500);
    else if (name === 'bookmarks')
      return new completion.FuzzyBookmarkCompleter();
    else if (name === 'tabs')
      return new completion.FuzzyTabCompleter();
    else if (name === 'tabsSorted')
      return new completion.MergingCompleter([getCompleter('tabs')]);
    else if (name === 'all')
      return new completion.MergingCompleter([
        getCompleter('smart'),
        getCompleter('bookmarks'),
        getCompleter('history'),
        getCompleter('tabs'),
        ]);
  }
  function getCompleter(name) {
    if (!(name in completers))
      completers[name] = createCompleter(name);
    return completers[name];
  }

  /** Trigger the fuzzy mode dialog */
  function start(name, reverseAction) {
    var completer = getCompleter(name);
    if (!fuzzyBox)
      fuzzyBox = new FuzzyBox(10, 300);
    completer.refresh();
    fuzzyBox.setCompleter(completer);
    fuzzyBox.show(reverseAction);
  }

  /** User interface for fuzzy completion */
  var FuzzyBox = function(maxResults, refreshInterval) {
    this.prompt = '>';
    this.maxResults = maxResults;
    this.refreshInterval = refreshInterval;
    this.initDom();
    this.reset();
  }
  FuzzyBox.prototype = {
    setCompleter: function(completer) {
      this.completer = completer;
      this.reset();
    },

    show: function(reverseAction) {
      this.reverseAction = reverseAction;
      this.box.style.display = 'block';
      var self = this;
      handlerStack.push({ keydown: function(event) { self.onKeydown(event); }});
    },

    hide: function() {
      this.box.style.display = 'none';
      handlerStack.pop();
    },

    reset: function() {
      this.query = '';
      // query used to filter the last completion result. We need this for asynchronous updating
      this.lastQuery = null;
      this.completions = [];
      this.selection = 0;
      // force synchronous updating so that the old results will not be flash up shortly
      this.update(true);
    },

    updateSelection: function() {
      for (var i = 0; i < this.completionList.children.length; ++i)
        this.completionList.children[i].className = (i == this.selection) ? 'selected' : '';
    },

    onKeydown: function(event) {
      var keyChar = getKeyChar(event);

      if (isEscape(event)) {
        this.hide();
      }

      // move selection with Up/Down, Tab/Shift-Tab, Ctrl-k/Ctrl-j
      else if (keyChar === 'up' || (event.keyCode == 9 && event.shiftKey)
              || (keyChar === 'k' && event.ctrlKey)) {
        if (this.selection > 0)
          this.selection -= 1;
        this.updateSelection();
      }
      else if (keyChar === 'down' || (event.keyCode == 9 && !event.shiftKey)
              || (keyChar === 'j' && isPrimaryModifierKey(event))) {
        if (this.selection < this.completions.length - 1)
          this.selection += 1;
        this.updateSelection();
      }

      else if (event.keyCode == keyCodes.backspace) {
        if (this.query.length > 0) {
          this.query = this.query.substr(0, this.query.length-1);
          this.update();
        }
      }

      // refresh with F5
      else if (keyChar == 'f5') {
        this.completer.refresh();
        this.update();
      }

      // use primary action with Enter. Holding down Shift/Ctrl uses the alternative action
      // (opening in new tab)
      else if (event.keyCode == keyCodes.enter) {
        this.update(true); // force synchronous update

        var alternative = (event.shiftKey || isPrimaryModifierKey(event));
        if (this.reverseAction)
          alternative = !alternative;
        this.completions[this.selection].action[alternative ? 1 : 0]();
        this.hide();
        this.reset();
      }

      else if (keyChar.length == 1) {
        this.query += keyChar;
        this.update();
      }

      event.stopPropagation();
      event.preventDefault();
      return true;
    },

    updateInput: function() {
      this.query = this.query.replace(/^\s*/, '');
      this.input.textContent = this.query;
    },

    updateCompletions: function() {
      if (this.query.length == 0) {
        this.completionList.style.display = 'none';
        return;
      }

      var self = this;
      this.completer.filter(this.query, function(completions) {
        self.completions = completions.slice(0, self.maxResults);

        // clear completions
        self.completionList.innerHTML = self.completions.map(function(completion) {
          return '
' + completion.render() + '
';
        }).join('');

        self.completionList.style.display = self.completions.length > 0 ? 'block' : 'none';
        self.updateSelection();
      });
    },

    update: function(sync) {
      sync = sync || false; // explicitely default to asynchronous updating
      this.updateInput();

      if (sync) {
        this.updateCompletions();
      } else {
        var self = this;
        // always update asynchronously for better user experience and to take some load off the CPU
        // (not every keystroke will cause a dedicated update)
        setTimeout(function() {
          if (self.query === self.lastQuery)
            return;
          self.lastQuery = self.query;
          self.updateCompletions();
        }, this.refreshInterval);
      }
    },

    initDom: function() {
      this.box = utils.createElementFromHtml(
        '
'+
          '
'+
            '' + utils.escapeHtml(this.prompt) + ' '+
            '
'+
          '

');
      this.box.style.display = 'none';
      document.body.appendChild(this.box);

      this.input          = document.getElementById("fuzzyboxInput");
      this.completionList = document.getElementById("fuzzyboxCompletions");
    },
  }

  // public interface
  return {
    activateAll:       function() { start('all',        false); },
    activateAllNewTab: function() { start('all',        true);  },
    activateTabs:      function() { start('tabsSorted', false); },
  }

})();
<|endoftext|>


<commit_before>/* global describe, before, after, it */

var webdriver = require('selenium-webdriver')

describe('hbm-react-components', function () {
  var driver

  before(function () {
    driver = new webdriver.Builder()
      .withCapabilities({
        'browserName': 'chrome',
        'platform': 'Windows XP',
        'version': '43.0',
        'username': process.env.SAUCE_USERNAME,
        'accessKey': process.env.SAUCE_ACCESS_KEY
      })
      .usingServer('http://' + process.env.SAUCE_USERNAME + ':' + process.env.SAUCE_ACCESS_KEY + '@ondemand.saucelabs.com:80/wd/hub')
      .build()
  })

  after(function (done) {
    driver.quit()
    // without timeout sauce labs tests do not end properly and
    // report "Your test errored. Test did not see a new command for 90 seconds. Timing out."
    setTimeout(function () {
      done()
    }, 1000)
  })

  it('should work', function (done) {
    driver.get('http://www.google.com')
    driver.getTitle().then(function (title) {
      console.log(title)
      done()
    })
  })
})

<commit_msg>get session from sauce labs<commit_after>/* global describe, before, after, it */

var webdriver = require('selenium-webdriver')

describe('hbm-react-components', function () {
  var driver

  before(function () {
    driver = new webdriver.Builder()
      .withCapabilities({
        'browserName': 'chrome',
        'platform': 'Windows XP',
        'version': '43.0',
        'username': process.env.SAUCE_USERNAME,
        'accessKey': process.env.SAUCE_ACCESS_KEY
      })
      .usingServer('http://' + process.env.SAUCE_USERNAME + ':' + process.env.SAUCE_ACCESS_KEY + '@ondemand.saucelabs.com:80/wd/hub')
      .build()
  })

  after(function (done) {
    // get sauce labs job id
    driver.getSession().then(function (sessionID) {
      console.log(sessionID)
      console.log(sessionID.id_)
      driver.quit()
      // without timeout sauce labs tests do not end properly and
      // report "Your test errored. Test did not see a new command for 90 seconds. Timing out."
      setTimeout(function () {
        done()
      }, 1000)
    })
  })

  it('should work', function (done) {
    driver.get('http://www.google.com')
    driver.getTitle().then(function (title) {
      console.log(title)
      done()
    })
  })
})

<|endoftext|>


<commit_before>
angular.module('jsonForms', [
    'ngRoute',
    'ui.bootstrap',
    'ui.validate',
    'ui.router',
    'ui.ace',
    'ui.grid',
    'ui.grid.pagination',
    'ui.grid.autoResize',
    'jsonForms.services',
    'jsonForms.data.send',
    'jsonForms.verticalLayout',
    'jsonForms.horizontalLayout',
    'jsonForms.label',
    'jsonForms.control',
    'jsonForms.table',
    'jsonForms.dataServices',
    'jsonForms.directives'
]);
<commit_msg>Removed ngRoute dependency<commit_after>
angular.module('jsonForms', [
    'ui.bootstrap',
    'ui.validate',
    'ui.router',
    'ui.ace',
    'ui.grid',
    'ui.grid.pagination',
    'ui.grid.autoResize',
    'jsonForms.services',
    'jsonForms.data.send',
    'jsonForms.verticalLayout',
    'jsonForms.horizontalLayout',
    'jsonForms.label',
    'jsonForms.control',
    'jsonForms.table',
    'jsonForms.dataServices',
    'jsonForms.directives'
]);
<|endoftext|>