<commit_before>// pumpapi.js
//
// The beating heart of a pumpin' good time
//
// Copyright 2011, StatusNet Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var Activity = require('./model/activity').Activity,
    PumpApp = require('./pumpapp').PumpApp,
    connect = require('connect'),
    User = require('./model/user').User,
    Edge = require('./model/edge').Edge,
    databank = require('databank'),
    _ = require('../public/javascript/underscore.js'),
    Step = require('step'),
    NoSuchThingError = databank.NoSuchThingError;

// Application controller

var PumpAPI = _.extend(_.clone(PumpApp), {

    bank: null,
    DEFAULT_USERS: 20,
    DEFAULT_ACTIVITIES: 20,

    initApp: function(app) {

        var i = 0, url, type;

        // Users
        app.get('/user/:nickname', this.reqUser, this.getUser);
        app.put('/user/:nickname', this.userAuth, this.reqUser, this.sameUser, this.putUser);
        app.del('/user/:nickname', this.userAuth, this.reqUser, this.sameUser, this.delUser);

        // Feeds

        app.post('/user/:nickname/feed', this.userAuth, this.reqUser, this.sameUser, this.postActivity);
        app.get('/user/:nickname/feed', this.reqUser, this.userStream); // XXX: privileged access when authenticated

        // Inboxen

        app.get('/user/:nickname/inbox', this.reqUser, this.userInbox);
        app.post('/user/:nickname/inbox', this.notYetImplemented);

        for (i = 0; i < Activity.objectTypes.length; i++) {

            type = Activity.objectTypes[i];

            url = '/' + type + '/' + ':uuid';

            // person

            app.get(url, this.requester(type), this.getter(type));
            app.put(url, this.userAuth, this.requester(type), this.authorOnly(type), this.putter(type));
            app.del(url, this.userAuth, this.requester(type), this.authorOnly(type), this.deleter(type));
        }

        // Activities

        app.get('/activity/:uuid', this.requester('activity'), this.getter('activity'));
        app.put('/activity/:uuid', this.userAuth, this.requester('activity'), this.actorOnly, this.putter('activity'));
        app.del('/activity/:uuid', this.userAuth, this.requester('activity'), this.actorOnly, this.deleter('activity'));

        // Global user list

        app.get('/users', this.listUsers);
        app.post('/users', this.createUser);

    },

    requester: function(type) {

        var Cls = Activity.toClass(type),
            pump = this;

        return function(req, res, next) {
            Cls.search({'uuid': req.params.uuid}, function(err, results) {
                if (err instanceof NoSuchThingError) {
                    pump.showError(res, err, 404);
                } else if (err) {
                    pump.showError(res, err);
                } else if (results.length === 0) {
                    pump.showError(res, new Error("Can't find a " + type + " with ID = " + req.params.uuid), 404);
                } else if (results.length > 1) {
                    pump.showError(res, new Error("Too many " + type + " objects with ID = " + req.params.uuid), 500);
                } else {
                    req[type] = results[0];
                    next();
                }
            });
        };
    },

    authorOnly: function(type) {
        var pump = this;
        return function(req, res, next) {
            var obj = req[type];

            if (obj && obj.author && obj.author.id == req.remoteUser.personId) {
                next();
            } else {
                pump.showError(res, new Error("Only the author can modify this object."), 403);
            }
        };
    },

    actorOnly: function(req, res, next) {
        var act = req.activity;

        if (act && act.actor && act.actor.id == req.remoteUser.personId) {
            next();
        } else {
            this.showError(res, new Error("Only the actor can modify this object."), 403);
        }
    },

    getter: function(type) {
        var pump = this;
        return function(req, res, next) {
            pump.showData(res, req[type]);
        };
    },

    putter: function(type) {
        var pump = this;
        return function(req, res, next) {
            var obj = req[type];
            obj.update(req.body, function(err, result) {
                if (err) {
                    pump.showError(res, err);
                } else {
                    pump.showData(res, result);
                }
            });
        };
    },

    deleter: function(type) {
        var pump = this;
        return function(req, res, next) {
            var obj = req[type];
            obj.del(function(err) {
                if (err) {
                    pump.showError(res, err);
                } else {
                    pump.showData(res, "Deleted");
                }
            });
        };
    },

    sameUser: function(req, res, next) {
        if (!req.remoteUser ||
            !req.user ||
            req.remoteUser.nickname != req.user.nickname) {
            this.showError(res, new Error("Not authorized"), 403);
        } else {
            next();
        }
    },

    getUser: function(req, res) {
        this.showData(res, req.user);
    },

    putUser: function(req, res, next) {

        var newUser = req.body;

        req.user.update(newUser, function(err, saved) {
            if (err) {
                this.showError(res, err);
            } else {
                saved.sanitize();
                this.showData(res, saved);
            }
        });
    },

    delUser: function(req, res, next) {
        req.user.del(function(err) {
            if (err instanceof NoSuchThingError) { // unusual
                this.showError(res, err, 404);
            } else if (err) {
                this.showError(res, err);
            } else {
                this.bank.decr('usercount', 0, function(err, value) {
                    if (err) {
                        this.showError(res, err);
                    } else {
                        this.showData(res, "Deleted");
                    }
                });
            }
        });
    },

    createUser: function (req, res, next) {
        var user, pump = PumpAPI;

        Step(
            function () {
                User.create(req.body, this);
            },
            function (err, value) {
                if (err) throw err;
                user = value;
                pump.bank.prepend('userlist', 0, user.nickname, this);
            },
            function (err, userList) {
                if (err) throw err;
                pump.bank.incr('usercount', 0, this);
            },
            function (err, userCount) {
                if (err) {
                    pump.showError(res, err);
                } else {
                    // Hide the password for output
                    user.sanitize();
                    pump.showData(res, user);
                }
            }
        );
    },

    listUsers: function(req, res, next) {
        var bank = this.bank,
            start, cnt, end,
            pump = this;

        var collection = {
            displayName: "Users of this service",
            id: this.makeURL("users"),
            objectTypes: ["user"]
        };

        start = (req.query.offset) ? parseInt(req.query.offset, 10) : 0;
        cnt = (req.query.cnt) ? parseInt(req.query.cnt, 10) : this.DEFAULT_USERS;
        end = start + cnt;

        Step(
            function () {
                bank.read('usercount', 0, this);
            },
            function(err, totalUsers) {
                if (err) throw err;
                collection.totalCount = totalUsers;
                bank.slice('userlist', 0, start, end, this);
            },
            function(err, userIds) {
                if (err) {
                    if (err instanceof NoSuchThingError) {
                        collection.items = [];
                        pump.showData(res, collection);
                    } else {
                        throw err;
                    }
                } else if (userIds.length === 0) {
                    collection.items = [];
                    pump.showData(res, collection);
                } else {
                    bank.readAll('user', userIds, this);
                }
            },
            function(err, userMap) {
                var users = [], id, user;
                if (err) throw err;

                for (id in userMap) {
                    user = new User(userMap[id]);
                    user.sanitize();
                    users.push(user);
                }
                users.sort(function(a, b) {
                    if (a.published > b.published) {
                        return -1;
                    } else if (a.published < b.published) {
                        return 1;
                    } else {
                        return 0;
                    }
                });
                collection.items = users;
                pump.showData(res, collection);
            }
        );
    },

    postActivity: function(req, res, next) {

        var activity = new Activity(req.body), pump = PumpAPI;

        Step(
            function() {
                // First, apply the activity
                activity.apply(req.person, this);
            },
            function(err) {
                if (err) throw err;
                // ...then persist...
                activity.save(this);
            },
            function(err, saved) {
                if (err) throw err;
                activity = saved;
                pump.addToOutbox(activity, req.person, this);
            },
            function(err) {
                if (err) {
                    pump.showError(res, err);
                } else {
                    // ...then show (possibly modified) results.
                    pump.showData(res, activity);
                    // ...then distribute.
                    process.nextTick(function() {
                        pump.distribute(activity, function(err) {});
                    });
                }
            }
        );
    },

    addToOutbox: function(activity, person, next) {
        var bank = this.bank;

        Step(
            function() {
                bank.prepend('outbox', person.id, activity.id, this);
            },
            function(err, value) {
                if (err) throw err;
                bank.incr('outboxcount', person.id, this);
            },
            function(err, value) {
                if (err) {
                    next(err);
                } else {
                    next(null);
                }
            }
        );
    },

    userStream: function(req, res, next) {

        var collection = {
            author: req.person,
            displayName: "Activities by " + (req.person.displayName || req.user.nickname),
            id: this.makeURL("user/" + req.user.nickname + "/feed"),
            objectTypes: ["activity"]
        };

        var start, cnt, end, pump = PumpAPI, bank = this.bank;

        start = (req.query.offset) ? parseInt(req.query.offset, 10) : 0;
        cnt = (req.query.cnt) ? parseInt(req.query.cnt, 10) : pump.DEFAULT_ACTIVITIES;
        end = start + cnt;

        Step(
            function() {
                bank.read('outboxcount', req.user.personId, this);
            },
            function(err, totalOutbox) {
                if (err) throw err;
                collection.totalCount = totalOutbox;
                req.user.getStream(start, end, this);
            },
            function(err, activities) {
                if (err) {
                    pump.showError(res, err);
                } else {
                    activities.forEach(function(el, i, arr) {
                        // remove internal uuid info, if any
                        delete el.actor;
                        delete el.uuid;
                    });

                    collection.items = activities;

                    pump.showData(res, collection);
                }
            }
        );
    },

    userInbox: function(req, res, next) {

        var collection = {
            author: req.person,
            displayName: "Activities for " + (req.person.displayName || req.user.nickname),
            id: this.makeURL("user/" + req.user.nickname + "/inbox"),
            objectTypes: ["activity"],
            items: []
        };
        var start, cnt, end, pump = PumpAPI, bank = this.bank;

        start = (req.query.offset) ? parseInt(req.query.offset, 10) : 0;
        cnt = (req.query.cnt) ? parseInt(req.query.cnt, 10) : this.DEFAULT_ACTIVITIES;
        end = start + cnt;

        Step(
            function() {
                bank.read('inboxcount', req.user.personId, this);
            },
            function(err, inboxCount) {
                if (err) throw err;
                collection.totalCount = inboxCount;
                bank.slice('inbox', req.user.personId, start, end, this);
            },
            function(err, inbox) {
                if (err) throw err;
                // FIXME: Activity.readAll() would be nicer here
                bank.readAll('activity', inbox, this);
            },
            function(err, activityMap) {
                var activities = [], id;

                if (err) {
                    // No inbox yet; show empty collection
                    if (err instanceof NoSuchThingError) {
                        collection.totalCount = 0;
                        pump.showData(res, collection);
                    } else {
                        pump.showError(res, err);
                    }
                    return;
                }

                // Convert from id => activity map to array

                for (id in activityMap) {
                    activities.push(activityMap[id]);
                }

                // Sort by published, reverse chron

                activities.sort(function(a, b) {
                    if (a.published > b.published) {
                        return -1;
                    } else if (a.published < b.published) {
                            return 1;
                    } else {
                        return 0;
                    }
                });

                // sanitize

                activities.forEach(function(el, i, arr) {
                    // remove internal uuid info, if any
                    delete el.uuid;
                });

                collection.items = activities;

                pump.showData(res, collection);
            }
        );
    },

    notYetImplemented: function(req, res, next) {
        PumpAPI.showError(res, new Error("Not yet implemented"));
    },

    port: null,
    hostname: null,

    makeURL: function(relative) {
        if (this.port != 80) {
            return 'http://'+this.hostname+':'+this.port+'/'+relative;
        } else {
            return 'http://'+this.hostname+'/'+relative;
        }
    },

    getSchema: function() {

        var i, type, Cls, schema = {};

        schema.activity = Activity.schema;
        schema.user = User.schema;
        schema.edge = Edge.schema;
        schema.inbox = {'pkey': 'id'};
        schema.inboxcount = {'pkey': 'id'};
        schema.outbox = {'pkey': 'id'};
        schema.outboxcount = {'pkey': 'id'};
        schema.userlist = {'pkey': 'id'};
        schema.usercount = {'pkey': 'id'};
        schema.feedcount = {'pkey': 'id'};

        for (i = 0; i < Activity.objectTypes.length; i++) {
            type = Activity.objectTypes[i];
            Cls = Activity.toClass(type);
            if (Cls.schema) {
                schema[type] = Cls.schema;
            } else {
                schema[type] = {'pkey': 'id',
                                'fields': ['updated', 'published', 'displayName', 'url'],
                                'indices': ['uuid', 'author.id']};
            }
        }

        return schema;
    },

    distribute: function(activity, callback) {
        var bank = this.bank;

        Step(
            function() {
                Edge.search({'to.id': activity.actor.id}, this);
            },
            function(err, follows) {
                if (err) throw err;
                var i, id, group = this.group();
                for (i = 0; i < follows.length; i++) {
                    id = follows[i].from.id;
                    // FIXME: check for local/remote distribution
                    // FIXME: trim long inboxen
                    bank.prepend('inbox', id, activity.id, group());
                    bank.incr('inboxcount', id, group());
                }
            },
            function(err) {
                if (err) {
                    callback(err);
                } else {
                    callback(null);
                }
            }
        );
    }
});

PumpAPI.userAuth = connect.basicAuth(PumpApp.checkCredentials);

// Make this more useful

_.bindAll(PumpAPI);

exports.PumpAPI = PumpAPI;
<commit_msg>handle missing outbox count<commit_after>// pumpapi.js
//
// The beating heart of a pumpin' good time
//
// Copyright 2011, StatusNet Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var Activity = require('./model/activity').Activity,
    PumpApp = require('./pumpapp').PumpApp,
    connect = require('connect'),
    User = require('./model/user').User,
    Edge = require('./model/edge').Edge,
    databank = require('databank'),
    _ = require('../public/javascript/underscore.js'),
    Step = require('step'),
    NoSuchThingError = databank.NoSuchThingError;

// Application controller

var PumpAPI = _.extend(_.clone(PumpApp), {

    bank: null,
    DEFAULT_USERS: 20,
    DEFAULT_ACTIVITIES: 20,

    initApp: function(app) {

        var i = 0, url, type;

        // Users
        app.get('/user/:nickname', this.reqUser, this.getUser);
        app.put('/user/:nickname', this.userAuth, this.reqUser, this.sameUser, this.putUser);
        app.del('/user/:nickname', this.userAuth, this.reqUser, this.sameUser, this.delUser);

        // Feeds

        app.post('/user/:nickname/feed', this.userAuth, this.reqUser, this.sameUser, this.postActivity);
        app.get('/user/:nickname/feed', this.reqUser, this.userStream); // XXX: privileged access when authenticated

        // Inboxen

        app.get('/user/:nickname/inbox', this.reqUser, this.userInbox);
        app.post('/user/:nickname/inbox', this.notYetImplemented);

        for (i = 0; i < Activity.objectTypes.length; i++) {

            type = Activity.objectTypes[i];

            url = '/' + type + '/' + ':uuid';

            // person

            app.get(url, this.requester(type), this.getter(type));
            app.put(url, this.userAuth, this.requester(type), this.authorOnly(type), this.putter(type));
            app.del(url, this.userAuth, this.requester(type), this.authorOnly(type), this.deleter(type));
        }

        // Activities

        app.get('/activity/:uuid', this.requester('activity'), this.getter('activity'));
        app.put('/activity/:uuid', this.userAuth, this.requester('activity'), this.actorOnly, this.putter('activity'));
        app.del('/activity/:uuid', this.userAuth, this.requester('activity'), this.actorOnly, this.deleter('activity'));

        // Global user list

        app.get('/users', this.listUsers);
        app.post('/users', this.createUser);

    },

    requester: function(type) {

        var Cls = Activity.toClass(type),
            pump = this;

        return function(req, res, next) {
            Cls.search({'uuid': req.params.uuid}, function(err, results) {
                if (err instanceof NoSuchThingError) {
                    pump.showError(res, err, 404);
                } else if (err) {
                    pump.showError(res, err);
                } else if (results.length === 0) {
                    pump.showError(res, new Error("Can't find a " + type + " with ID = " + req.params.uuid), 404);
                } else if (results.length > 1) {
                    pump.showError(res, new Error("Too many " + type + " objects with ID = " + req.params.uuid), 500);
                } else {
                    req[type] = results[0];
                    next();
                }
            });
        };
    },

    authorOnly: function(type) {
        var pump = this;
        return function(req, res, next) {
            var obj = req[type];

            if (obj && obj.author && obj.author.id == req.remoteUser.personId) {
                next();
            } else {
                pump.showError(res, new Error("Only the author can modify this object."), 403);
            }
        };
    },

    actorOnly: function(req, res, next) {
        var act = req.activity;

        if (act && act.actor && act.actor.id == req.remoteUser.personId) {
            next();
        } else {
            this.showError(res, new Error("Only the actor can modify this object."), 403);
        }
    },

    getter: function(type) {
        var pump = this;
        return function(req, res, next) {
            pump.showData(res, req[type]);
        };
    },

    putter: function(type) {
        var pump = this;
        return function(req, res, next) {
            var obj = req[type];
            obj.update(req.body, function(err, result) {
                if (err) {
                    pump.showError(res, err);
                } else {
                    pump.showData(res, result);
                }
            });
        };
    },

    deleter: function(type) {
        var pump = this;
        return function(req, res, next) {
            var obj = req[type];
            obj.del(function(err) {
                if (err) {
                    pump.showError(res, err);
                } else {
                    pump.showData(res, "Deleted");
                }
            });
        };
    },

    sameUser: function(req, res, next) {
        if (!req.remoteUser ||
            !req.user ||
            req.remoteUser.nickname != req.user.nickname) {
            this.showError(res, new Error("Not authorized"), 403);
        } else {
            next();
        }
    },

    getUser: function(req, res) {
        this.showData(res, req.user);
    },

    putUser: function(req, res, next) {

        var newUser = req.body;

        req.user.update(newUser, function(err, saved) {
            if (err) {
                this.showError(res, err);
            } else {
                saved.sanitize();
                this.showData(res, saved);
            }
        });
    },

    delUser: function(req, res, next) {
        req.user.del(function(err) {
            if (err instanceof NoSuchThingError) { // unusual
                this.showError(res, err, 404);
            } else if (err) {
                this.showError(res, err);
            } else {
                this.bank.decr('usercount', 0, function(err, value) {
                    if (err) {
                        this.showError(res, err);
                    } else {
                        this.showData(res, "Deleted");
                    }
                });
            }
        });
    },

    createUser: function (req, res, next) {
        var user, pump = PumpAPI;

        Step(
            function () {
                User.create(req.body, this);
            },
            function (err, value) {
                if (err) throw err;
                user = value;
                pump.bank.prepend('userlist', 0, user.nickname, this);
            },
            function (err, userList) {
                if (err) throw err;
                pump.bank.incr('usercount', 0, this);
            },
            function (err, userCount) {
                if (err) {
                    pump.showError(res, err);
                } else {
                    // Hide the password for output
                    user.sanitize();
                    pump.showData(res, user);
                }
            }
        );
    },

    listUsers: function(req, res, next) {
        var bank = this.bank,
            start, cnt, end,
            pump = this;

        var collection = {
            displayName: "Users of this service",
            id: this.makeURL("users"),
            objectTypes: ["user"]
        };

        start = (req.query.offset) ? parseInt(req.query.offset, 10) : 0;
        cnt = (req.query.cnt) ? parseInt(req.query.cnt, 10) : this.DEFAULT_USERS;
        end = start + cnt;

        Step(
            function () {
                bank.read('usercount', 0, this);
            },
            function(err, totalUsers) {
                if (err) throw err;
                collection.totalCount = totalUsers;
                bank.slice('userlist', 0, start, end, this);
            },
            function(err, userIds) {
                if (err) {
                    if (err instanceof NoSuchThingError) {
                        collection.items = [];
                        pump.showData(res, collection);
                    } else {
                        throw err;
                    }
                } else if (userIds.length === 0) {
                    collection.items = [];
                    pump.showData(res, collection);
                } else {
                    bank.readAll('user', userIds, this);
                }
            },
            function(err, userMap) {
                var users = [], id, user;
                if (err) throw err;

                for (id in userMap) {
                    user = new User(userMap[id]);
                    user.sanitize();
                    users.push(user);
                }
                users.sort(function(a, b) {
                    if (a.published > b.published) {
                        return -1;
                    } else if (a.published < b.published) {
                        return 1;
                    } else {
                        return 0;
                    }
                });
                collection.items = users;
                pump.showData(res, collection);
            }
        );
    },

    postActivity: function(req, res, next) {

        var activity = new Activity(req.body), pump = PumpAPI;

        Step(
            function() {
                // First, apply the activity
                activity.apply(req.person, this);
            },
            function(err) {
                if (err) throw err;
                // ...then persist...
                activity.save(this);
            },
            function(err, saved) {
                if (err) throw err;
                activity = saved;
                pump.addToOutbox(activity, req.person, this);
            },
            function(err) {
                if (err) {
                    pump.showError(res, err);
                } else {
                    // ...then show (possibly modified) results.
                    pump.showData(res, activity);
                    // ...then distribute.
                    process.nextTick(function() {
                        pump.distribute(activity, function(err) {});
                    });
                }
            }
        );
    },

    addToOutbox: function(activity, person, next) {
        var bank = this.bank;

        Step(
            function() {
                bank.prepend('outbox', person.id, activity.id, this);
            },
            function(err, value) {
                if (err) throw err;
                bank.incr('outboxcount', person.id, this);
            },
            function(err, value) {
                if (err) {
                    next(err);
                } else {
                    next(null);
                }
            }
        );
    },

    userStream: function(req, res, next) {

        var collection = {
            author: req.person,
            displayName: "Activities by " + (req.person.displayName || req.user.nickname),
            id: this.makeURL("user/" + req.user.nickname + "/feed"),
            objectTypes: ["activity"],
            items: []
        };

        var start, cnt, end, pump = PumpAPI, bank = this.bank;

        start = (req.query.offset) ? parseInt(req.query.offset, 10) : 0;
        cnt = (req.query.cnt) ? parseInt(req.query.cnt, 10) : pump.DEFAULT_ACTIVITIES;
        end = start + cnt;

        Step(
            function() {
                bank.read('outboxcount', req.user.personId, this);
            },
            function(err, totalOutbox) {
                if (err) {
                    if (err instanceof NoSuchThingError) {
                        pump.showData(res, collection);
                    } else {
                        throw err;
                    }
                } else {
                    req.user.getStream(start, end, this);
                }
            },
            function(err, activities) {
                if (err) {
                    pump.showError(res, err);
                } else {
                    activities.forEach(function(el, i, arr) {
                        // remove internal uuid info, if any
                        delete el.actor;
                        delete el.uuid;
                    });

                    collection.items = activities;

                    pump.showData(res, collection);
                }
            }
        );
    },

    userInbox: function(req, res, next) {

        var collection = {
            author: req.person,
            displayName: "Activities for " + (req.person.displayName || req.user.nickname),
            id: this.makeURL("user/" + req.user.nickname + "/inbox"),
            objectTypes: ["activity"],
            items: []
        };
        var start, cnt, end, pump = PumpAPI, bank = this.bank;

        start = (req.query.offset) ? parseInt(req.query.offset, 10) : 0;
        cnt = (req.query.cnt) ? parseInt(req.query.cnt, 10) : this.DEFAULT_ACTIVITIES;
        end = start + cnt;

        Step(
            function() {
                bank.read('inboxcount', req.user.personId, this);
            },
            function(err, inboxCount) {
                if (err) throw err;
                collection.totalCount = inboxCount;
                bank.slice('inbox', req.user.personId, start, end, this);
            },
            function(err, inbox) {
                if (err) throw err;
                // FIXME: Activity.readAll() would be nicer here
                bank.readAll('activity', inbox, this);
            },
            function(err, activityMap) {
                var activities = [], id;

                if (err) {
                    // No inbox yet; show empty collection
                    if (err instanceof NoSuchThingError) {
                        collection.totalCount = 0;
                        pump.showData(res, collection);
                    } else {
                        pump.showError(res, err);
                    }
                    return;
                }

                // Convert from id => activity map to array

                for (id in activityMap) {
                    activities.push(activityMap[id]);
                }

                // Sort by published, reverse chron

                activities.sort(function(a, b) {
                    if (a.published > b.published) {
                        return -1;
                    } else if (a.published < b.published) {
                            return 1;
                    } else {
                        return 0;
                    }
                });

                // sanitize

                activities.forEach(function(el, i, arr) {
                    // remove internal uuid info, if any
                    delete el.uuid;
                });

                collection.items = activities;

                pump.showData(res, collection);
            }
        );
    },

    notYetImplemented: function(req, res, next) {
        PumpAPI.showError(res, new Error("Not yet implemented"));
    },

    port: null,
    hostname: null,

    makeURL: function(relative) {
        if (this.port != 80) {
            return 'http://'+this.hostname+':'+this.port+'/'+relative;
        } else {
            return 'http://'+this.hostname+'/'+relative;
        }
    },

    getSchema: function() {

        var i, type, Cls, schema = {};

        schema.activity = Activity.schema;
        schema.user = User.schema;
        schema.edge = Edge.schema;
        schema.inbox = {'pkey': 'id'};
        schema.inboxcount = {'pkey': 'id'};
        schema.outbox = {'pkey': 'id'};
        schema.outboxcount = {'pkey': 'id'};
        schema.userlist = {'pkey': 'id'};
        schema.usercount = {'pkey': 'id'};
        schema.feedcount = {'pkey': 'id'};

        for (i = 0; i < Activity.objectTypes.length; i++) {
            type = Activity.objectTypes[i];
            Cls = Activity.toClass(type);
            if (Cls.schema) {
                schema[type] = Cls.schema;
            } else {
                schema[type] = {'pkey': 'id',
                                'fields': ['updated', 'published', 'displayName', 'url'],
                                'indices': ['uuid', 'author.id']};
            }
        }

        return schema;
    },

    distribute: function(activity, callback) {
        var bank = this.bank;

        Step(
            function() {
                Edge.search({'to.id': activity.actor.id}, this);
            },
            function(err, follows) {
                if (err) throw err;
                var i, id, group = this.group();
                for (i = 0; i < follows.length; i++) {
                    id = follows[i].from.id;
                    // FIXME: check for local/remote distribution
                    // FIXME: trim long inboxen
                    bank.prepend('inbox', id, activity.id, group());
                    bank.incr('inboxcount', id, group());
                }
            },
            function(err) {
                if (err) {
                    callback(err);
                } else {
                    callback(null);
                }
            }
        );
    }
});

PumpAPI.userAuth = connect.basicAuth(PumpApp.checkCredentials);

// Make this more useful

_.bindAll(PumpAPI);

exports.PumpAPI = PumpAPI;
<|endoftext|>


<commit_before>var numCells = 12;
var cellSize = 50;
var borderSize = 50;

var numStartBalls = 10;
var numBallsPerTurn = 3;
var numBallTypes = 8;

var colorMap = [
    0xFF0000,
    0xFFAA00,
    0xFFFF00,
    0x0000FF,
    0x00FF00,
    0x00FFFF,
    0x8800AA,
    0xFF00FF,
    0x888800
];

function FiveBalls(PIXI) {
    this.PIXI = PIXI;

    this.init = function() {
        console.log("Hey");
        //Create the renderer
        var renderer = PIXI.autoDetectRenderer(256, 256);
        document.body.appendChild(renderer.view);
        var stage = new PIXI.Container();
        renderer.view.style.border = "1px dashed black";

        var width = numCells * cellSize + 2 * borderSize;
        var height = numCells * cellSize + 2 * borderSize;

        renderer.resize(width, height);
        renderer.backgroundColor = 0x223355;
        renderer.render(stage);
        console.log("Starting game loop");
        var board = new Board(stage);
        board.init(stage);

        var gameLoop = function() {
            requestAnimationFrame(gameLoop);
            renderer.render(stage);
            update();
            render();
        }

        gameLoop();
    };

    function update() {};
    function render() {};
}

function Board(stage) {
    this.tileMatrix = new Array(numCells);
    this.ballMatrix = new Array(numCells);
    this.tileTexture = new PIXI.Texture.fromImage("static/tile.png")
    this.ballTexture = new PIXI.Texture.fromImage("static/ball.png")
    this.stage = stage;
    this.selectedBall = null;
    this.init = function() {
        // Init tiles
        for (var i = 0; i < numCells; i++) {
            this.tileMatrix = new Array(numCells);
            for (var j = 0; j < numCells; j++) {
              this.addTile(i,j);
            }
        }

        // Init balls
        for (var i = 0; i < numCells; i++) {
            this.ballMatrix[i] = new Array(numCells);
        }

        // Place first balls
        var count = numStartBalls;
        while (count > 0) {
            var x = Math.floor(Math.random() * numCells);
            var y = Math.floor(Math.random() * numCells);
            if (this.ballMatrix[x][y] != null) {
                continue;
            }

            var color = Math.floor(Math.random() * numBallTypes);
            this.addBall(x, y, color);
            count = count - 1;
        }

    }

    this.addTile = function(x, y) {
        var board = this;
        var tileSprite = new PIXI.Sprite(this.tileTexture);
        var tile = new Tile(x, y, tileSprite);
        tileSprite.x = borderSize + x * cellSize;
        tileSprite.y = borderSize + y * cellSize;
        tileSprite.interactive = true;
        tileSprite.on('mouseup', function(mousedata) {
            board.tileClicked(tile);
        });
        this.stage.addChild(tileSprite);
    }

    this.addBall = function(x, y, colorIndex) {
        var ballSprite = new PIXI.Sprite(this.ballTexture);
        ballSprite.x = this.getCoordsForCell(x, y)[0];
        ballSprite.y = this.getCoordsForCell(x, y)[1];
        ballSprite.tint = colorMap[colorIndex];
        var ball = new Ball(x, y, ballSprite);

        this.ballMatrix[x][y] = ball;
        this.stage.addChild(ballSprite);
    }

    this.moveBall = function(ball, x, y) {
        var coords = this.getCoordsForCell(x, y);
        ball.ballSprite.x = coords[0];
        ball.ballSprite.y = coords[1];
    }

    this.removeBall = function(ball) {
        this.stage.removeChild(ballSprite);
        this.ballMatrix[ball.x,
            ball.y] = null;
    }

    this.tileClicked = function(tile) {
        var ball = this.ballMatrix[tile.x][tile.y];
        if (ball != null) {
            console.log("From board, ball clicked : " + ball.x + " , " + ball.y);
        } else {

            console.log("From board, tiled clicked : " + tile.x + " , " + tile.y);
        }
    }

    this.getCoordsForCell = function(x, y) {
        return [
            borderSize + x * cellSize,
            borderSize + y * cellSize
        ];
    }
}

function Tile(x, y, sprite) {
    this.x = x;
    this.y = y;
    this.sprite = sprite;
}

function Ball(x, y, colorIndex) {
    this.x = x;
    this.y = y;
    this.colorIndex = colorIndex;

    this.onClick = function(mouseEvent) {}

}<commit_msg>[balls] They move now<commit_after>var numCells = 12;
var cellSize = 50;
var borderSize = 50;

var numStartBalls = 10;
var numBallsPerTurn = 3;
var numBallTypes = 8;

var colorMap = [
    0xFF0000,
    0xFFAA00,
    0xFFFF00,
    0x0000FF,
    0x00FF00,
    0x00FFFF,
    0x8800AA,
    0xFF00FF,
    0x888800
];

function FiveBalls(PIXI) {
    this.PIXI = PIXI;

    this.init = function() {
        console.log("Hey");
        //Create the renderer
        var renderer = PIXI.autoDetectRenderer(256, 256);
        document.body.appendChild(renderer.view);
        var stage = new PIXI.Container();
        renderer.view.style.border = "1px dashed black";

        var width = numCells * cellSize + 2 * borderSize;
        var height = numCells * cellSize + 2 * borderSize;

        renderer.resize(width, height);
        renderer.backgroundColor = 0x223355;
        renderer.render(stage);
        console.log("Starting game loop");
        var board = new Board(stage);
        board.init(stage);

        var gameLoop = function() {
            requestAnimationFrame(gameLoop);
            renderer.render(stage);
            update();
            render();
        }

        gameLoop();
    };

    function update() {};
    function render() {};
}

function Board(stage) {
    this.tileMatrix = new Array(numCells);
    this.ballMatrix = new Array(numCells);
    this.tileTexture = new PIXI.Texture.fromImage("static/tile.png")
    this.ballTexture = new PIXI.Texture.fromImage("static/ball.png")
    this.stage = stage;
    this.selectedBall = null;
    this.init = function() {
        // Init tiles
        for (var i = 0; i < numCells; i++) {
            this.tileMatrix = new Array(numCells);
            for (var j = 0; j < numCells; j++) {
                this.addTile(i, j);
            }
        }

        // Init balls
        for (var i = 0; i < numCells; i++) {
            this.ballMatrix[i] = new Array(numCells);
        }

        // Place first balls
        var count = numStartBalls;
        while (count > 0) {
            var x = Math.floor(Math.random() * numCells);
            var y = Math.floor(Math.random() * numCells);
            if (this.ballMatrix[x][y] != null) {
                continue;
            }

            var color = Math.floor(Math.random() * numBallTypes);
            this.addBall(x, y, color);
            count = count - 1;
        }

    }

    this.addTile = function(x, y) {
        var board = this;
        var tileSprite = new PIXI.Sprite(this.tileTexture);
        var tile = new Tile(x, y, tileSprite);
        tileSprite.x = borderSize + x * cellSize;
        tileSprite.y = borderSize + y * cellSize;
        tileSprite.interactive = true;
        tileSprite.on('mouseup', function(mousedata) {
            board.tileClicked(tile);
        });
        this.stage.addChild(tileSprite);
    }

    this.addBall = function(x, y, colorIndex) {
        var ballSprite = new PIXI.Sprite(this.ballTexture);
        ballSprite.x = this.getCoordsForCell(x, y)[0];
        ballSprite.y = this.getCoordsForCell(x, y)[1];
        ballSprite.tint = colorMap[colorIndex];
        var ball = new Ball(x, y, colorIndex, ballSprite);
        this.ballMatrix[x][y] = ball;
        this.stage.addChild(ballSprite);
    }

    this.moveBall = function(ball, x, y) {
        var coords = this.getCoordsForCell(x, y);
        ball.sprite.x = coords[0];
        ball.sprite.y = coords[1];
        ball.x = x;
        ball.y = y;
        this.ballMatrix[x][y] =ball;
    }

    this.removeBall = function(ball) {
        this.stage.removeChild(ballSprite);
        this.ballMatrix[ball.x,
            ball.y] = null;
    }

    this.tileClicked = function(tile) {
        var ball = this.ballMatrix[tile.x][tile.y];
        if (ball != null) {
            this.selectedBall = ball;
        } else {
            if (this.selectedBall) {
                this.moveBall(this.selectedBall, tile.x, tile.y);
                this.selectedBall = null;
            }
        }
    }

    this.getCoordsForCell = function(x, y) {
        return [
            borderSize + x * cellSize,
            borderSize + y * cellSize
        ];
    }
}

function Tile(x, y, sprite) {
    this.x = x;
    this.y = y;
    this.sprite = sprite;
}

function Ball(x, y, colorIndex, sprite) {
    this.x = x;
    this.y = y;
    this.colorIndex = colorIndex;
    this.sprite = sprite;

    this.onClick = function(mouseEvent) {}

}<|endoftext|>


<commit_before>$(function () {

    var secsToText = function(total) {
        total = Math.floor(total / 60);
        var m = total % 60; total = Math.floor(total / 60);
        var h = total % 24; total = Math.floor(total / 24);
        var d = total % 30 % 7;
        var o = Math.floor(total / 30);
        var w = Math.floor(total % 30 / 7);

        var result = "";
        if (o) result += o + (o === 1 ? " month " : " months ");
        if (w) result += w + (w === 1 ? " week " : " weeks ");
        if (d) result += d + (d === 1 ? " day " : " days ");
        if (h) result += h + (h === 1 ? " hour " : " hours ");
        if (m) result += m + (m === 1 ? " minute " : " minutes ");

        return result;
    }

    var periodSlider = document.getElementById("period-slider");
    noUiSlider.create(periodSlider, {
        start: [20],
        connect: "lower",
        range: {
            'min': [60, 60],
            '30%': [3600, 3600],
            '50%': [86400, 86400],
            '80%': [604800, 604800],
            'max': 2592000,
        },
        pips: {
            mode: 'values',
            values: [60, 1800, 3600, 43200, 86400, 604800, 2592000],
            density: 5,
            format: {
                to: secsToText,
                from: function() {}
            }
        }
    });

    periodSlider.noUiSlider.on("update", function(a, b, value) {
        var rounded = Math.round(value);
        $("#period-slider-value").text(secsToText(rounded));
        $("#update-timeout-timeout").val(rounded);
    });

    var graceSlider = document.getElementById("grace-slider");
    noUiSlider.create(graceSlider, {
        start: [20],
        connect: "lower",
        range: {
            'min': [60, 60],
            '30%': [3600, 3600],
            '50%': [86400, 86400],
            '80%': [604800, 604800],
            'max': 2592000,
        },
        pips: {
            mode: 'values',
            values: [60, 1800, 3600, 43200, 86400, 604800, 2592000],
            density: 5,
            format: {
                to: secsToText,
                from: function() {}
            }
        }
    });

    graceSlider.noUiSlider.on("update", function(a, b, value) {
        var rounded = Math.round(value);
        $("#grace-slider-value").text(secsToText(rounded));
        $("#update-timeout-grace").val(rounded);
    });

    $('[data-toggle="tooltip"]').tooltip();

    $(".my-checks-name").click(function() {
        var $this = $(this);

        $("#update-name-form").attr("action", $this.data("url"));
        $("#update-name-input").val($this.data("name"));
        $("#update-tags-input").val($this.data("tags"));
        $('#update-name-modal').modal("show");
        $("#update-name-input").focus();

        return false;
    });

    $(".timeout-grace").click(function() {
        var $this = $(this);

        $("#update-timeout-form").attr("action", $this.data("url"));
        periodSlider.noUiSlider.set($this.data("timeout"))
        graceSlider.noUiSlider.set($this.data("grace"))
        $('#update-timeout-modal').modal({"show":true, "backdrop":"static"});

        return false;
    });

    $(".check-menu-remove").click(function() {
        var $this = $(this);

        $("#remove-check-form").attr("action", $this.data("url"));
        $(".remove-check-name").text($this.data("name"));
        $('#remove-check-modal').modal("show");

        return false;
    });

    $("#show-urls").click(function() {
        $("#show-urls").addClass("active");
        $(".my-checks-url").removeClass("off");

        $("#show-emails").removeClass("active");
        $(".my-checks-email").addClass("off");
    });

    $("#show-emails").click(function() {
        $("#show-urls").removeClass("active");
        $(".my-checks-url").addClass("off");

        $("#show-emails").addClass("active");
        $(".my-checks-email").removeClass("off");
    });

    $(".selectable").click(function() {
        $(this).tooltip("hide");
        this.select();
    });

    $("#my-checks-tags button").click(function() {
        // .active has not been updated yet by bootstrap code,
        // so cannot use it
        $(this).toggleClass('checked');

        // Make a list of currently checked tags:
        var checked = [];
        $("#my-checks-tags button.checked").each(function(index, el) {
            checked.push(el.textContent);
        });

        // No checked tags: show all
        if (checked.length == 0) {
            $("#checks-table tr.checks-row").show();
            $("#checks-list > li").show();
            return;
        }

        function applyFilters(index, element) {
            var tags = $(".my-checks-name", element).data("tags").split(" ");
            for (var i=0, tag; tag=checked[i]; i++) {
                if (tags.indexOf(tag) == -1) {
                    $(element).hide();
                    return;
                }
            }

            $(element).show();
        }

        // Desktop: for each row, see if it needs to be shown or hidden
        $("#checks-table tr.checks-row").each(applyFilters);
        // Mobile: for each list item, see if it needs to be shown or hidden
        $("#checks-list > li").each(applyFilters);

    });

});
<commit_msg>secsToText uses same algorithm as Python version. Position slider labels evenly.<commit_after>$(function () {

    var MINUTE = {name: "minute", nsecs: 60};
    var HOUR = {name: "hour", nsecs: MINUTE.nsecs * 60};
    var DAY = {name: "day", nsecs: HOUR.nsecs * 24};
    var WEEK = {name: "week", nsecs: DAY.nsecs * 7};
    var MONTH = {name: "month", nsecs: DAY.nsecs * 30};
    var UNITS = [MONTH, WEEK, DAY, HOUR, MINUTE];

    var secsToText = function(total) {
        var remainingSeconds = Math.floor(total);
        var result = "";
        for (var i=0, unit; unit=UNITS[i]; i++) {
            var count = Math.floor(remainingSeconds / unit.nsecs);
            remainingSeconds = remainingSeconds % unit.nsecs;

            if (count == 1) {
                result += "1 " + unit.name + " ";
            }

            if (count > 1) {
                result += count + " " + unit.name + "s ";
            }
        }

        return result;
    }

    var periodSlider = document.getElementById("period-slider");
    noUiSlider.create(periodSlider, {
        start: [20],
        connect: "lower",
        range: {
            'min': [60, 60],
            '33%': [3600, 3600],
            '66%': [86400, 86400],
            '83%': [604800, 604800],
            'max': 2592000,
        },
        pips: {
            mode: 'values',
            values: [60, 1800, 3600, 43200, 86400, 604800, 2592000],
            density: 4,
            format: {
                to: secsToText,
                from: function() {}
            }
        }
    });

    periodSlider.noUiSlider.on("update", function(a, b, value) {
        var rounded = Math.round(value);
        $("#period-slider-value").text(secsToText(rounded));
        $("#update-timeout-timeout").val(rounded);
    });

    var graceSlider = document.getElementById("grace-slider");
    noUiSlider.create(graceSlider, {
        start: [20],
        connect: "lower",
        range: {
            'min': [60, 60],
            '33%': [3600, 3600],
            '66%': [86400, 86400],
            '83%': [604800, 604800],
            'max': 2592000,
        },
        pips: {
            mode: 'values',
            values: [60, 1800, 3600, 43200, 86400, 604800, 2592000],
            density: 5,
            format: {
                to: secsToText,
                from: function() {}
            }
        }
    });

    graceSlider.noUiSlider.on("update", function(a, b, value) {
        var rounded = Math.round(value);
        $("#grace-slider-value").text(secsToText(rounded));
        $("#update-timeout-grace").val(rounded);
    });

    $('[data-toggle="tooltip"]').tooltip();

    $(".my-checks-name").click(function() {
        var $this = $(this);

        $("#update-name-form").attr("action", $this.data("url"));
        $("#update-name-input").val($this.data("name"));
        $("#update-tags-input").val($this.data("tags"));
        $('#update-name-modal').modal("show");
        $("#update-name-input").focus();

        return false;
    });

    $(".timeout-grace").click(function() {
        var $this = $(this);

        $("#update-timeout-form").attr("action", $this.data("url"));
        periodSlider.noUiSlider.set($this.data("timeout"))
        graceSlider.noUiSlider.set($this.data("grace"))
        $('#update-timeout-modal').modal({"show":true, "backdrop":"static"});

        return false;
    });

    $(".check-menu-remove").click(function() {
        var $this = $(this);

        $("#remove-check-form").attr("action", $this.data("url"));
        $(".remove-check-name").text($this.data("name"));
        $('#remove-check-modal').modal("show");

        return false;
    });

    $("#show-urls").click(function() {
        $("#show-urls").addClass("active");
        $(".my-checks-url").removeClass("off");

        $("#show-emails").removeClass("active");
        $(".my-checks-email").addClass("off");
    });

    $("#show-emails").click(function() {
        $("#show-urls").removeClass("active");
        $(".my-checks-url").addClass("off");

        $("#show-emails").addClass("active");
        $(".my-checks-email").removeClass("off");
    });

    $(".selectable").click(function() {
        $(this).tooltip("hide");
        this.select();
    });

    $("#my-checks-tags button").click(function() {
        // .active has not been updated yet by bootstrap code,
        // so cannot use it
        $(this).toggleClass('checked');

        // Make a list of currently checked tags:
        var checked = [];
        $("#my-checks-tags button.checked").each(function(index, el) {
            checked.push(el.textContent);
        });

        // No checked tags: show all
        if (checked.length == 0) {
            $("#checks-table tr.checks-row").show();
            $("#checks-list > li").show();
            return;
        }

        function applyFilters(index, element) {
            var tags = $(".my-checks-name", element).data("tags").split(" ");
            for (var i=0, tag; tag=checked[i]; i++) {
                if (tags.indexOf(tag) == -1) {
                    $(element).hide();
                    return;
                }
            }

            $(element).show();
        }

        // Desktop: for each row, see if it needs to be shown or hidden
        $("#checks-table tr.checks-row").each(applyFilters);
        // Mobile: for each list item, see if it needs to be shown or hidden
        $("#checks-list > li").each(applyFilters);

    });

});
<|endoftext|>


<commit_before>$(document).ready(function() {
    $.urlParam = function(name){
        var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
        if (results==null){
            return null;
        }
        else{
            return results[1] || 0;
        }
    }

    var uuid = $.urlParam('uuid') || okular.device_uuid || 'testdevice';

    function updateData() {
        $.getJSON('/events/' + uuid, function(data) {
            var now = new Date();

            $('#meeting div').html('
Room available
');
            $('#next').html('');
            $('#finish, #cancel').hide();

            var filled = false;
            $.each(data, function(index, evt) {
                var start = new Date(evt.start.dateTime),
                    end = new Date(evt.end.dateTime);
                var attendees = [evt.creator];
                if ('attendees' in evt) {
                    attendees = attendees.concat(evt.attendees);
                }
                attendees = $.map(attendees, function(a) {
                    return a.displayName || a.email;
                });
                if (start.getTime() < now.getTime() && now.getTime() < end.getTime() && !filled) {
                    $('#meeting div').html('
' + evt.summary + '

' + time(start) + '-' + time(end) + '

Atendees:

' + attendees.join(', ') + '
');
                    filled = true;
                    $('#finish, #cancel').show();
                } else {
                    $('#next').append('<button type="button" class="btn btn-success">' + time(start) + '-' + time(end) + '
' + evt.summary + '</button>');
                }
            });
        });
    }

    function time(date) {
        var hours = date.getHours(),
            minutes = date.getMinutes(),
            out = '';
        if (hours < 10)
            out += '0';
        out += hours + ':';
        if (minutes < 10)
            out += '0'

        return out + minutes;
    }

    $('#book').click(function(e) {
        $.get('/create_action/' + uuid + '/newevent', function(data) {
            console.log(data);
            $('#qr').show();
            //$('#qr').html('Scan this code

 or open
' + data + '
 in your browser.');
            $('#qr').html('Scan this code

');
            new QRCode("qrcode", {
                text: data,
                width: 300,
                height: 300
            });
            $('#qr').tmList();

            $('body').one('click', function() {
                $('#qr').hide();
                okular.add({
                    width: 600,
                    height: 600
                });
            });
        });
    });

    $('#cancel').click(function(e) {
        $.get('/create_action/' + uuid + '/cancelevent', function(data) {
            $('#qr').show();
            //$('#qr').html('Scan this code

 or open
' + data + '
 in your browser.');
            $('#qr').html('Scan this code

');
            new QRCode("qrcode", {
                text: data,
                width: 300,
                height: 300
            });
            $('#qr').tmList();

            $('body').one('click', function() {
                $('#qr').hide();
                okular.add({
                    width: 600,
                    height: 600
                });
            });
        });
    });

    $('#finish').click(function(e) {
        $.get('/create_action/' + uuid + '/finishevent', function(data) {
            $('#qr').show();
            //$('#qr').html('Scan this code

 or open
' + data + '
 in your browser.');
            $('#qr').html('Scan this code

');
            new QRCode("qrcode", {
                text: data,
                width: 300,
                height: 300
            });
            $('#qr').tmList();

            $('body').one('click', function() {
                $('#qr').hide();
                okular.add({
                    width: 600,
                    height: 600
                });
            });
        });
    });

    okular.init({
        width: 800,
        height: 600,
        debug: false
    });

    updateData();
    setInterval(updateData, 60000);
});
<commit_msg>Make only necessary dom manipulations to minimize image changes.<commit_after>$(document).ready(function() {
    $.urlParam = function(name){
        var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
        if (results==null){
            return null;
        }
        else{
            return results[1] || 0;
        }
    }

    var uuid = $.urlParam('uuid') || okular.device_uuid || 'testdevice';

    function updateData() {
        $.getJSON('/events/' + uuid, function(data) {
            var now = new Date();

            var filled = false;
            var next = '';
            $.each(data, function(index, evt) {
                var start = new Date(evt.start.dateTime),
                    end = new Date(evt.end.dateTime);
                var attendees = [evt.creator];
                if ('attendees' in evt) {
                    attendees = attendees.concat(evt.attendees);
                }
                attendees = $.map(attendees, function(a) {
                    return a.displayName || a.email;
                });
                if (start.getTime() < now.getTime() && now.getTime() < end.getTime() && !filled) {
                    var text = '
' + evt.summary + '

' + time(start) + '-' + time(end) + '

Atendees:

' + attendees.join(', ') + '
';
                    if ($.trim($('#meeting div').html()) != text) {
                        $('#meeting div').html(text);
                        $('#finish, #cancel').show();
                    }
                    filled = true;
                } else {
                    next += '<button type="button" class="btn btn-success">' + time(start) + '-' + time(end) + '
' + evt.summary + '</button>';
                }
            });
            if ($.trim($('#next').html()) != next) {
                $('#next').html(next);
            }
            if (!filled && $.trim($('#meeting div').html()) != '
Room available
') {
                $('#meeting div').html('
Room available
');
                $('#finish, #cancel').hide();
            }
        });
    }

    function time(date) {
        var hours = date.getHours(),
            minutes = date.getMinutes(),
            out = '';
        if (hours < 10)
            out += '0';
        out += hours + ':';
        if (minutes < 10)
            out += '0'

        return out + minutes;
    }

    $('#book').click(function(e) {
        $.get('/create_action/' + uuid + '/newevent', function(data) {
            console.log(data);
            $('#qr').show();
            //$('#qr').html('Scan this code

 or open
' + data + '
 in your browser.');
            $('#qr').html('Scan this code

');
            new QRCode("qrcode", {
                text: data,
                width: 300,
                height: 300
            });
            $('#qr').tmList();

            $('body').one('click', function() {
                $('#qr').hide();
                okular.add({
                    width: 600,
                    height: 600
                });
            });
        });
    });

    $('#cancel').click(function(e) {
        $.get('/create_action/' + uuid + '/cancelevent', function(data) {
            $('#qr').show();
            //$('#qr').html('Scan this code

 or open
' + data + '
 in your browser.');
            $('#qr').html('Scan this code

');
            new QRCode("qrcode", {
                text: data,
                width: 300,
                height: 300
            });
            $('#qr').tmList();

            $('body').one('click', function() {
                $('#qr').hide();
                okular.add({
                    width: 600,
                    height: 600
                });
            });
        });
    });

    $('#finish').click(function(e) {
        $.get('/create_action/' + uuid + '/finishevent', function(data) {
            $('#qr').show();
            //$('#qr').html('Scan this code

 or open
' + data + '
 in your browser.');
            $('#qr').html('Scan this code

');
            new QRCode("qrcode", {
                text: data,
                width: 300,
                height: 300
            });
            $('#qr').tmList();

            $('body').one('click', function() {
                $('#qr').hide();
                okular.add({
                    width: 600,
                    height: 600
                });
            });
        });
    });

    okular.init({
        width: 800,
        height: 600,
        debug: false
    });

    updateData();
    setInterval(updateData, 60000);
    $('#finish, #cancel').hide();
});
<|endoftext|>


<commit_before>$(document).ready(function() {
	$('#menu').click(function() { window.location='menu' } );

    var mapCanvas = $('#map-canvas');
    var mapOptions = {
	center: { lat: 60.1841396, lng: 24.8300838 },
	zoom: 12
    };
    var map = new google.maps.Map(mapCanvas[0], mapOptions);
    map.data.setStyle(function(feature) {
	var type = feature.getProperty('type');
	var title = feature.getProperty('title');
	if (type === 'raw-point') {
	    var pointColor = getActivityColor(feature.getProperty('activity'));
	    return {
		icon: {
		    path: google.maps.SymbolPath.CIRCLE,
		    scale: 3,
		    strokeColor: pointColor,
		    strokeOpacity: 0.5
		},
		title: title
	    };
	}
    });

    var currentDate;

    function update(date) {
	currentDate = date;

	mapCanvas.css('opacity', 0.1);
	map.data.forEach(function(feature) {
	    map.data.remove(feature);
	});
	$.getJSON('/energymap/geojson?date=' + date, function(response) {
	    map.data.addGeoJson(response);
	    mapCanvas.css('opacity', '');
		if (response.features.length > 1) {
			var bounds = new google.maps.LatLngBounds();
			response.features.forEach(function (feature) {
				var coords = feature.geometry.coordinates;
		    	bounds.extend(new google.maps.LatLng(coords[1],coords[0]));
			});
			map.fitBounds(bounds);
		}
	});
    }

    function hashchange() {
        var date = location.hash.split("#").splice(-1)[0];
        var today = (new Date()).toISOString().slice(0, 10);
        update(date || today);
    }

    $(window).on("hashchange", hashchange);
    hashchange();
});
<commit_msg>fix: Map call path.<commit_after>$(document).ready(function() {
	$('#menu').click(function() { window.location='menu' } );

    var mapCanvas = $('#map-canvas');
    var mapOptions = {
	center: { lat: 60.1841396, lng: 24.8300838 },
	zoom: 12
    };
    var map = new google.maps.Map(mapCanvas[0], mapOptions);
    map.data.setStyle(function(feature) {
	var type = feature.getProperty('type');
	var title = feature.getProperty('title');
	if (type === 'raw-point') {
	    var pointColor = getActivityColor(feature.getProperty('activity'));
	    return {
		icon: {
		    path: google.maps.SymbolPath.CIRCLE,
		    scale: 3,
		    strokeColor: pointColor,
		    strokeOpacity: 0.5
		},
		title: title
	    };
	}
    });

    var currentDate;

    function update(date) {
	currentDate = date;

	mapCanvas.css('opacity', 0.1);
	map.data.forEach(function(feature) {
	    map.data.remove(feature);
	});
	$.getJSON('energymap/geojson?date=' + date, function(response) {
	    map.data.addGeoJson(response);
	    mapCanvas.css('opacity', '');
		if (response.features.length > 1) {
			var bounds = new google.maps.LatLngBounds();
			response.features.forEach(function (feature) {
				var coords = feature.geometry.coordinates;
		    	bounds.extend(new google.maps.LatLng(coords[1],coords[0]));
			});
			map.fitBounds(bounds);
		}
	});
    }

    function hashchange() {
        var date = location.hash.split("#").splice(-1)[0];
        var today = (new Date()).toISOString().slice(0, 10);
        update(date || today);
    }

    $(window).on("hashchange", hashchange);
    hashchange();
});
<|endoftext|>


<commit_before>;(function( _, $, App ){

    /// Variables: -------------------------------------------------------------

    var $player;
    var $state;
    var $song_name;
    var $play;
    var $stop;

    /// Exports: ---------------------------------------------------------------

    App.Player = {
        update:         update,
        showStation:    showStation,
    };

    /// Init: ------------------------------------------------------------------

    $( init );

    /// Functions: -------------------------------------------------------------

    function init() {

        $player =       $( "#player" );
        $state =        $player.find( ".state" );
        $song_name =    $player.find( ".song-name" );
        $play =         $player.find( ".play" );
        $stop =         $player.find( ".stop" );

        $play.on( "click", togglePlay );
        $stop.on( "click", stop );

        update();
    };

    function update() {

        showStation( App.SoundPlayer.getCurrentStation() );
    };

    function togglePlay() {

        if ( !$play.hasClass( "disabled" )) {
            var station = App.SoundPlayer.getCurrentStation();
            if ( station ) {
                if ( station.playing ) {
                    App.AppController.pauseStation( station );
                } else {
                    App.AppController.playStation( station );
                }
            } else {
                showStation();
            }
        }
    };

    function stop() {

        if ( !$stop.hasClass( "disabled" )) {
            App.AppController.stop();
        }
    };

    function showStation( station ) {

        if ( !station ) {
            $state.html( "Stopped." );
            $song_name.html( "" );
            $play.addClass( "disabled" ).html( App.LABEL_PLAY );
            $stop.addClass( "disabled" );
        } else {
            $song_name.html( station.name );
            $play.removeClass( "disabled" );
            $stop.removeClass( "disabled" );
            if ( station.playing ) {
                $state.html( "Playing:" );
                $play.html( App.LABEL_PAUSE );
            } else {
                $state.html( "Paused:" );
                $play.html( App.LABEL_PLAY );
            }
        }
    };

})( window._, window.$, window.App );
<commit_msg>Fixed station name not showing in #player.<commit_after>;(function( _, $, App ){

    /// Variables: -------------------------------------------------------------

    var $player;
    var $state;
    var $song_name;
    var $play;
    var $stop;

    /// Exports: ---------------------------------------------------------------

    App.Player = {
        update:         update,
        showStation:    showStation,
    };

    /// Init: ------------------------------------------------------------------

    $( init );

    /// Functions: -------------------------------------------------------------

    function init() {

        $player =       $( "#player" );
        $state =        $player.find( ".state" );
        $song_name =    $player.find( ".song-name" );
        $play =         $player.find( ".play" );
        $stop =         $player.find( ".stop" );

        $play.on( "click", togglePlay );
        $stop.on( "click", stop );

        update();
    };

    function update() {

        showStation( App.SoundPlayer.getCurrentStation() );
    };

    function togglePlay() {

        if ( !$play.hasClass( "disabled" )) {
            var station = App.SoundPlayer.getCurrentStation();
            if ( station ) {
                if ( station.playing ) {
                    App.AppController.pauseStation( station );
                } else {
                    App.AppController.playStation( station );
                }
            } else {
                showStation();
            }
        }
    };

    function stop() {

        if ( !$stop.hasClass( "disabled" )) {
            App.AppController.stop();
        }
    };

    function showStation( station ) {

        if ( !station ) {
            $state.html( "Stopped." );
            $song_name.html( "" );
            $play.addClass( "disabled" ).html( App.LABEL_PLAY );
            $stop.addClass( "disabled" );
        } else {
            $song_name.html( station.info.name );
            $play.removeClass( "disabled" );
            $stop.removeClass( "disabled" );
            if ( station.playing ) {
                $state.html( "Playing:" );
                $play.html( App.LABEL_PAUSE );
            } else {
                $state.html( "Paused:" );
                $play.html( App.LABEL_PLAY );
            }
        }
    };

})( window._, window.$, window.App );
<|endoftext|>


<commit_before>var blocks = [],
    menu = false;

//TODO: Don't hard code this.
var filters = [
    "% Bachelors or More",
    "% Commercial Land Use",
    "% High School or More",
    "% In Labor Force",
    "% Industrial Land Use",
    "% Other Land Use",
    "% Owner Occupied",
    "% Renter Occupied",
    "% Residential Land Use",
    "% School Aged Children",
    "% Vacant Land Use",
    "% on Public Assistance",
    "All Other Population",
    "American Indian Population",
    "Animal Calls Rate",
    "Asian Population",
    "Assessed Value 2012",
    "Black Population",
    "Blighted Properties",
    "Block Group ID",
    "Certs of Compliance/Occupancy Issued",
    "Child Poverty Rate",
    "City Demolitions",
    "Estimated Tax Bill 2012",
    "Forclosure Starts",
    "Household Size",
    "Households",
    "Land Value Per Acre 2012",
    "Median Household Income",
    "Nuisance Calls Rate",
    "Out of Town Owners",
    "Parcels",
    "PopDen",
    "Population",
    "Population 10-17",
    "Population 18-21",
    "Population 22-29",
    "Population 30's",
    "Population 40's",
    "Population 50's",
    "Population 60's",
    "Population Over 70",
    "Population Under 9",
    "Poverty Rate",
    "Property Crime Rate",
    "School Kids",
    "Square Miles",
    "Unemployment Rate",
    "Vacant Lots",
    "Vacant Structures",
    "Vice Calls Rate",
    "Violent Crime Rate",
    "White Population"
];

$(document).ready(function () {
    var main = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            attribution: 'Map data © OpenStreetMap contributors',
            minZoom: 5,
            maxZoom: 18,
        });
        counties = L.layerGroup(),
        current_towns = L.layerGroup();

    // generate list of filter choices.
    for (var filter in filters){
        $('#filterList').append('
' + filters[filter] + '
').click(changeType);
    }

    window.map = L.map('map-canvas', {
        center: [43.160552395407585, -77.60656356811523],
        zoom: 13,
        layers: [
            main
         ]
    });

    new L.Control.GeoSearch({
        provider: new L.GeoSearch.Provider.Esri(),
        showMarker: false
    }).addTo(map);

    $.getJSON('/static/data/US/NY/055/US-NY-055-SHAPE.json', function (features) {
        csvToJson('/static/data/US/NY/055/US-NY-055-STATS.csv', function (stats) {
            L.geoJson(features, {
                style: function (feature) {
                    var id = feature.properties['GEOID'];
                           return {
                               fillColor: "blue",
                               fillOpacity: 0,
                               opacity: 0
                            }
                },
                onEachFeature: function (feature, layer) {
                    layer.index = stats['Block Group ID'].indexOf(feature.properties.GEOID);
                    window.stats = stats;
                    if(layer.index !== -1) {
                        blocks.push(layer);
                    }

                    layer.on('mouseover', function (event) {
                        layer.setStyle({ fillColor: 'red' });
                    });

                    layer.on('mouseout', function (event) {
                        layer.setStyle({ fillColor: 'blue' });
                    });

                    layer.on('click', function (event) {
                        //TODO: Create a popup menu with more information
                    });
                }
            }).addTo(map);
            populateScale('Violent Crime Rate')

        });
    });
});

function changeType(event) {
    populateScale(event.target.innerHTML);
    $('#filter').text(event.target.innerHTML);
}

function populateScale (label) {
    stats[label] = stats[label].map(Number);

    var min = d3.min(stats[label]),
        max = d3.max(stats[label]),
        scale = d3.scale.linear();
    scale.domain([min, max])
      .range([0, 0.9]);

    blocks.forEach(function (block) {
        var val = parseFloat(stats[label][block.index]),
            o = scale(val);
        block.setStyle({fillOpacity: o});

        var rank = getRanking(label, val);

        var html = 'Block Group ID: ' + stats['Block Group ID'][block.index] + '
'
            + 'Median Value: ' + stats[label][block.index] + '
'
            + 'Rank: #' + rank + '/' + blocks.length;

        block.bindPopup(html);
    });
}

function getRanking(label, val) {
    var sorted = stats[label].map(Number).sort();
    return sorted.indexOf(val) + 1;
}
<commit_msg>Changed the location of the map tiles to use HTTPS<commit_after>var blocks = [],
    menu = false;

//TODO: Don't hard code this.
var filters = [
    "% Bachelors or More",
    "% Commercial Land Use",
    "% High School or More",
    "% In Labor Force",
    "% Industrial Land Use",
    "% Other Land Use",
    "% Owner Occupied",
    "% Renter Occupied",
    "% Residential Land Use",
    "% School Aged Children",
    "% Vacant Land Use",
    "% on Public Assistance",
    "All Other Population",
    "American Indian Population",
    "Animal Calls Rate",
    "Asian Population",
    "Assessed Value 2012",
    "Black Population",
    "Blighted Properties",
    "Block Group ID",
    "Certs of Compliance/Occupancy Issued",
    "Child Poverty Rate",
    "City Demolitions",
    "Estimated Tax Bill 2012",
    "Forclosure Starts",
    "Household Size",
    "Households",
    "Land Value Per Acre 2012",
    "Median Household Income",
    "Nuisance Calls Rate",
    "Out of Town Owners",
    "Parcels",
    "PopDen",
    "Population",
    "Population 10-17",
    "Population 18-21",
    "Population 22-29",
    "Population 30's",
    "Population 40's",
    "Population 50's",
    "Population 60's",
    "Population Over 70",
    "Population Under 9",
    "Poverty Rate",
    "Property Crime Rate",
    "School Kids",
    "Square Miles",
    "Unemployment Rate",
    "Vacant Lots",
    "Vacant Structures",
    "Vice Calls Rate",
    "Violent Crime Rate",
    "White Population"
];

$(document).ready(function () {
    var main = new L.TileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            attribution: 'Map data © OpenStreetMap contributors',
            minZoom: 5,
            maxZoom: 18,
        });
        counties = L.layerGroup(),
        current_towns = L.layerGroup();

    // generate list of filter choices.
    for (var filter in filters){
        $('#filterList').append('
' + filters[filter] + '
').click(changeType);
    }

    window.map = L.map('map-canvas', {
        center: [43.160552395407585, -77.60656356811523],
        zoom: 13,
        layers: [
            main
         ]
    });

    new L.Control.GeoSearch({
        provider: new L.GeoSearch.Provider.Esri(),
        showMarker: false
    }).addTo(map);

    $.getJSON('/static/data/US/NY/055/US-NY-055-SHAPE.json', function (features) {
        csvToJson('/static/data/US/NY/055/US-NY-055-STATS.csv', function (stats) {
            L.geoJson(features, {
                style: function (feature) {
                    var id = feature.properties['GEOID'];
                           return {
                               fillColor: "blue",
                               fillOpacity: 0,
                               opacity: 0
                            }
                },
                onEachFeature: function (feature, layer) {
                    layer.index = stats['Block Group ID'].indexOf(feature.properties.GEOID);
                    window.stats = stats;
                    if(layer.index !== -1) {
                        blocks.push(layer);
                    }

                    layer.on('mouseover', function (event) {
                        layer.setStyle({ fillColor: 'red' });
                    });

                    layer.on('mouseout', function (event) {
                        layer.setStyle({ fillColor: 'blue' });
                    });

                    layer.on('click', function (event) {
                        //TODO: Create a popup menu with more information
                    });
                }
            }).addTo(map);
            populateScale('Violent Crime Rate')

        });
    });
});

function changeType(event) {
    populateScale(event.target.innerHTML);
    $('#filter').text(event.target.innerHTML);
}

function populateScale (label) {
    stats[label] = stats[label].map(Number);

    var min = d3.min(stats[label]),
        max = d3.max(stats[label]),
        scale = d3.scale.linear();
    scale.domain([min, max])
      .range([0, 0.9]);

    blocks.forEach(function (block) {
        var val = parseFloat(stats[label][block.index]),
            o = scale(val);
        block.setStyle({fillOpacity: o});

        var rank = getRanking(label, val);

        var html = 'Block Group ID: ' + stats['Block Group ID'][block.index] + '
'
            + 'Median Value: ' + stats[label][block.index] + '
'
            + 'Rank: #' + rank + '/' + blocks.length;

        block.bindPopup(html);
    });
}

function getRanking(label, val) {
    var sorted = stats[label].map(Number).sort();
    return sorted.indexOf(val) + 1;
}
<|endoftext|>


<commit_before>// Exported for unit testing
exports.is_using_input_method = false;

exports.narrow_or_search_for_term = function (search_string) {
    const search_query_box = $("#search_query");
    if (exports.is_using_input_method) {
        // Neither narrow nor search when using input tools as
        // `updater` is also triggered when 'enter' is triggered
        // while using input tool
        return search_query_box.val();
    }
    ui_util.change_tab_to('#home');

    let operators;
    if (page_params.search_pills_enabled) {
        // We have to take care to append the new pill before calling this
        // function, so that the base_query includes the suggestion selected
        // along with query corresponding to the existing pills.
        const base_query = search_pill.get_search_string_for_current_filter(
            search_pill_widget.widget);
        operators = Filter.parse(base_query);
    } else {
        operators = Filter.parse(search_string);
    }
    narrow.activate(operators, {trigger: 'search'});

    // It's sort of annoying that this is not in a position to
    // blur the search box, because it means that Esc won't
    // unnarrow, it'll leave the searchbox.

    // Narrowing will have already put some operators in the search box,
    // so leave the current text in.
    if (!page_params.search_pills_enabled) {
        search_query_box.blur();
    }
    return search_query_box.val();
};

function update_buttons_with_focus(focused) {
    const search_query_box = $('#search_query');

    // Show buttons iff the search input is focused, or has non-empty contents,
    // or we are narrowed.
    if (focused
        || search_query_box.val()
        || narrow_state.active()) {
        $('.search_button').prop('disabled', false);
    }
}

exports.update_button_visibility = function () {
    update_buttons_with_focus($('#search_query').is(':focus'));
};

exports.initialize = function () {
    const search_query_box = $('#search_query');
    const searchbox_form = $('#searchbox_form');
    const searchbox = $('#searchbox');

    // Data storage for the typeahead.
    // This maps a search string to an object with a "description" field.
    // (It's a bit of legacy that we have an object with only one important
    // field.  There's also a "search_string" field on each element that actually
    // just represents the key of the hash, so it's redundant.)
    let search_map = new Map();

    search_query_box.typeahead({
        source: function (query) {
            let base_query = '';
            if (page_params.search_pills_enabled) {
                base_query = search_pill.get_search_string_for_current_filter(
                    search_pill_widget.widget);
            }
            const suggestions = search_suggestion.get_suggestions(base_query, query);
            // Update our global search_map hash
            search_map = suggestions.lookup_table;
            return suggestions.strings;
        },
        fixed: true,
        items: search_suggestion.max_num_of_search_results,
        helpOnEmptyStrings: true,
        naturalSearch: true,
        highlighter: function (item) {
            const obj = search_map.get(item);
            return obj.description;
        },
        matcher: function () {
            return true;
        },
        updater: function (search_string) {
            if (page_params.search_pills_enabled) {
                search_pill.append_search_string(search_string,
                                                 search_pill_widget.widget);
                return search_query_box.val();
            }
            return exports.narrow_or_search_for_term(search_string);
        },
        sorter: function (items) {
            return items;
        },
        stopAdvance: page_params.search_pills_enabled,
        advanceKeyCodes: [8],

        on_move: function () {
            if (page_params.search_pills_enabled) {
                ui_util.place_caret_at_end(search_query_box[0]);
                return true;
            }
        },
        // Use our custom typeahead `on_escape` hook to exit
        // the search bar as soon as the user hits Esc.
        on_escape: tab_bar.exit_search,
    });

    searchbox_form.on('compositionend', () => {
        // Set `is_using_input_method` to true if enter is pressed to exit
        // the input tool popover and get the text in the search bar. Then
        // we suppress searching triggered by this enter key by checking
        // `is_using_input_method` before searching.
        // More details in the commit message that added this line.
        exports.is_using_input_method = true;
    });

    searchbox_form.keydown((e) => {
        exports.update_button_visibility();
        const code = e.which;
        if (code === 13 && search_query_box.is(":focus")) {
            // Don't submit the form so that the typeahead can instead
            // handle our Enter keypress. Any searching that needs
            // to be done will be handled in the keyup.
            return false;
        }
    }).keyup((e) => {
        if (exports.is_using_input_method) {
            exports.is_using_input_method = false;
            return;
        }
        const code = e.which;
        if (code === 13 && search_query_box.is(":focus")) {
            // We just pressed enter and the box had focus, which
            // means we didn't use the typeahead at all.  In that
            // case, we should act as though we're searching by
            // operators.  (The reason the other actions don't call
            // this codepath is that they first all blur the box to
            // indicate that they've done what they need to do)

            // Pill is already added during keydown event of input pills.
            exports.narrow_or_search_for_term(search_query_box.val());
            search_query_box.blur();
            update_buttons_with_focus(false);
        }
    });

    // Some of these functions don't actually need to be exported,
    // but the code was moved here from elsewhere, and it would be
    // more work to re-order everything and make them private.

    search_query_box.on('focus', exports.focus_search);
    search_query_box.on('blur', (e) => {
        // The search query box is a visual cue as to
        // whether search or narrowing is active.  If
        // the user blurs the search box, then we should
        // update the search string to reflect the current
        // narrow (or lack of narrow).
        //
        // But we can't do this right away, because
        // selecting something in the typeahead menu causes
        // the box to lose focus a moment before.
        //
        // The workaround is to check 100ms later -- long
        // enough for the search to have gone through, but
        // short enough that the user won't notice (though
        // really it would be OK if they did).

        if (page_params.search_pills_enabled) {
            const pill_id = $(e.relatedTarget).closest(".pill").data('id');
            const search_pill = search_pill_widget.widget.getByID(pill_id);
            if (search_pill) {
                // The searchbox loses focus while the search
                // pill element gains focus.
                // We do not consider the searchbox to actually
                // lose focus when a pill inside it gets selected
                // or deleted by a click.
                return;
            }
        }
        setTimeout(() => {
            exports.update_button_visibility();
        }, 100);
    });

    if (page_params.search_pills_enabled) {
        // Uses jquery instead of pure css as the `:focus` event occurs on `#search_query`,
        // while we want to add box-shadow to `#searchbox`. This could have been done
        // with `:focus-within` CSS selector, but it is not supported in IE or Opera.
        searchbox.on('focusout', function () {
            tab_bar.close_search_bar_and_open_narrow_description();
            searchbox.css({"box-shadow": "unset"});
        });
    }
};

exports.focus_search = function () {
    // The search bar is not focused yet, but will be.
    update_buttons_with_focus(true);
};

exports.initiate_search = function () {
    tab_bar.open_search_bar_and_close_narrow_description();
    $('#searchbox').css({"box-shadow": "inset 0px 0px 0px 2px hsl(204, 20%, 74%)"});
    $('#search_query').typeahead('lookup').select();
    if (page_params.search_pills_enabled) {
        $('#search_query').focus();
        ui_util.place_caret_at_end($('#search_query')[0]);
    }
};

exports.clear_search_form = function () {
    $('#search_query').val('');
    $('#search_query').blur();
    $('.search_button').prop('disabled', true);
};

window.search = exports;
<commit_msg>search: Fix ESLint prefer-arrow-callback error.<commit_after>// Exported for unit testing
exports.is_using_input_method = false;

exports.narrow_or_search_for_term = function (search_string) {
    const search_query_box = $("#search_query");
    if (exports.is_using_input_method) {
        // Neither narrow nor search when using input tools as
        // `updater` is also triggered when 'enter' is triggered
        // while using input tool
        return search_query_box.val();
    }
    ui_util.change_tab_to('#home');

    let operators;
    if (page_params.search_pills_enabled) {
        // We have to take care to append the new pill before calling this
        // function, so that the base_query includes the suggestion selected
        // along with query corresponding to the existing pills.
        const base_query = search_pill.get_search_string_for_current_filter(
            search_pill_widget.widget);
        operators = Filter.parse(base_query);
    } else {
        operators = Filter.parse(search_string);
    }
    narrow.activate(operators, {trigger: 'search'});

    // It's sort of annoying that this is not in a position to
    // blur the search box, because it means that Esc won't
    // unnarrow, it'll leave the searchbox.

    // Narrowing will have already put some operators in the search box,
    // so leave the current text in.
    if (!page_params.search_pills_enabled) {
        search_query_box.blur();
    }
    return search_query_box.val();
};

function update_buttons_with_focus(focused) {
    const search_query_box = $('#search_query');

    // Show buttons iff the search input is focused, or has non-empty contents,
    // or we are narrowed.
    if (focused
        || search_query_box.val()
        || narrow_state.active()) {
        $('.search_button').prop('disabled', false);
    }
}

exports.update_button_visibility = function () {
    update_buttons_with_focus($('#search_query').is(':focus'));
};

exports.initialize = function () {
    const search_query_box = $('#search_query');
    const searchbox_form = $('#searchbox_form');
    const searchbox = $('#searchbox');

    // Data storage for the typeahead.
    // This maps a search string to an object with a "description" field.
    // (It's a bit of legacy that we have an object with only one important
    // field.  There's also a "search_string" field on each element that actually
    // just represents the key of the hash, so it's redundant.)
    let search_map = new Map();

    search_query_box.typeahead({
        source: function (query) {
            let base_query = '';
            if (page_params.search_pills_enabled) {
                base_query = search_pill.get_search_string_for_current_filter(
                    search_pill_widget.widget);
            }
            const suggestions = search_suggestion.get_suggestions(base_query, query);
            // Update our global search_map hash
            search_map = suggestions.lookup_table;
            return suggestions.strings;
        },
        fixed: true,
        items: search_suggestion.max_num_of_search_results,
        helpOnEmptyStrings: true,
        naturalSearch: true,
        highlighter: function (item) {
            const obj = search_map.get(item);
            return obj.description;
        },
        matcher: function () {
            return true;
        },
        updater: function (search_string) {
            if (page_params.search_pills_enabled) {
                search_pill.append_search_string(search_string,
                                                 search_pill_widget.widget);
                return search_query_box.val();
            }
            return exports.narrow_or_search_for_term(search_string);
        },
        sorter: function (items) {
            return items;
        },
        stopAdvance: page_params.search_pills_enabled,
        advanceKeyCodes: [8],

        on_move: function () {
            if (page_params.search_pills_enabled) {
                ui_util.place_caret_at_end(search_query_box[0]);
                return true;
            }
        },
        // Use our custom typeahead `on_escape` hook to exit
        // the search bar as soon as the user hits Esc.
        on_escape: tab_bar.exit_search,
    });

    searchbox_form.on('compositionend', () => {
        // Set `is_using_input_method` to true if enter is pressed to exit
        // the input tool popover and get the text in the search bar. Then
        // we suppress searching triggered by this enter key by checking
        // `is_using_input_method` before searching.
        // More details in the commit message that added this line.
        exports.is_using_input_method = true;
    });

    searchbox_form.keydown((e) => {
        exports.update_button_visibility();
        const code = e.which;
        if (code === 13 && search_query_box.is(":focus")) {
            // Don't submit the form so that the typeahead can instead
            // handle our Enter keypress. Any searching that needs
            // to be done will be handled in the keyup.
            return false;
        }
    }).keyup((e) => {
        if (exports.is_using_input_method) {
            exports.is_using_input_method = false;
            return;
        }
        const code = e.which;
        if (code === 13 && search_query_box.is(":focus")) {
            // We just pressed enter and the box had focus, which
            // means we didn't use the typeahead at all.  In that
            // case, we should act as though we're searching by
            // operators.  (The reason the other actions don't call
            // this codepath is that they first all blur the box to
            // indicate that they've done what they need to do)

            // Pill is already added during keydown event of input pills.
            exports.narrow_or_search_for_term(search_query_box.val());
            search_query_box.blur();
            update_buttons_with_focus(false);
        }
    });

    // Some of these functions don't actually need to be exported,
    // but the code was moved here from elsewhere, and it would be
    // more work to re-order everything and make them private.

    search_query_box.on('focus', exports.focus_search);
    search_query_box.on('blur', (e) => {
        // The search query box is a visual cue as to
        // whether search or narrowing is active.  If
        // the user blurs the search box, then we should
        // update the search string to reflect the current
        // narrow (or lack of narrow).
        //
        // But we can't do this right away, because
        // selecting something in the typeahead menu causes
        // the box to lose focus a moment before.
        //
        // The workaround is to check 100ms later -- long
        // enough for the search to have gone through, but
        // short enough that the user won't notice (though
        // really it would be OK if they did).

        if (page_params.search_pills_enabled) {
            const pill_id = $(e.relatedTarget).closest(".pill").data('id');
            const search_pill = search_pill_widget.widget.getByID(pill_id);
            if (search_pill) {
                // The searchbox loses focus while the search
                // pill element gains focus.
                // We do not consider the searchbox to actually
                // lose focus when a pill inside it gets selected
                // or deleted by a click.
                return;
            }
        }
        setTimeout(() => {
            exports.update_button_visibility();
        }, 100);
    });

    if (page_params.search_pills_enabled) {
        // Uses jquery instead of pure css as the `:focus` event occurs on `#search_query`,
        // while we want to add box-shadow to `#searchbox`. This could have been done
        // with `:focus-within` CSS selector, but it is not supported in IE or Opera.
        searchbox.on('focusout', () => {
            tab_bar.close_search_bar_and_open_narrow_description();
            searchbox.css({"box-shadow": "unset"});
        });
    }
};

exports.focus_search = function () {
    // The search bar is not focused yet, but will be.
    update_buttons_with_focus(true);
};

exports.initiate_search = function () {
    tab_bar.open_search_bar_and_close_narrow_description();
    $('#searchbox').css({"box-shadow": "inset 0px 0px 0px 2px hsl(204, 20%, 74%)"});
    $('#search_query').typeahead('lookup').select();
    if (page_params.search_pills_enabled) {
        $('#search_query').focus();
        ui_util.place_caret_at_end($('#search_query')[0]);
    }
};

exports.clear_search_form = function () {
    $('#search_query').val('');
    $('#search_query').blur();
    $('.search_button').prop('disabled', true);
};

window.search = exports;
<|endoftext|>