<commit_before>const socketio = require('socket.io');
const Socket = require('./socket.js');
const jwt = require('jsonwebtoken');
const _ = require('underscore');
const moment = require('moment');

const logger = require('./log.js');
const version = moment(require('../version.js'));
const PendingGame = require('./pendinggame.js');
const GameRouter = require('./gamerouter.js');
const MessageRepository = require('./repositories/messageRepository.js');
const DeckRepository = require('./repositories/deckRepository.js');

class Lobby {
    constructor(server, options = {}) {
        this.sockets = {};
        this.users = {};
        this.games = {};
        this.config = options.config;
        this.messageRepository = options.messageRepository || new MessageRepository(this.config.dbPath);
        this.deckRepository = options.deckRepository || new DeckRepository(this.config.dbPath);
        this.router = options.router || new GameRouter(this.config);
        this.router.on('onGameClosed', this.onGameClosed.bind(this));
        this.router.on('onPlayerLeft', this.onPlayerLeft.bind(this));
        this.router.on('onWorkerStarted', this.onWorkerStarted.bind(this));
        this.router.on('onWorkerTimedOut', this.onWorkerTimedOut.bind(this));

        this.io = options.io || socketio(server);
        this.io.set('heartbeat timeout', 30000);
        this.io.use(this.handshake.bind(this));
        this.io.on('connection', this.onConnection.bind(this));
    }

    // External methods
    getStatus() {
        var nodeStatus = this.router.getNodeStatus();

        return nodeStatus;
    }

    disableNode(nodeName) {
        return this.router.disableNode(nodeName);
    }

    enableNode(nodeName) {
        return this.router.enableNode(nodeName);
    }

    debugDump() {
        var games = _.map(this.games, game => {
            var players = _.map(game.players, player => {
                return {
                    name: player.name,
                    left: player.left,
                    disconnected: player.disconnected,
                    id: player.id
                };
            });

            var spectators = _.map(game.spectators, spectator => {
                return {
                    name: spectator.name,
                    id: spectator.id
                };
            });

            return {
                name: game.name,
                players: players,
                spectators: spectators,
                id: game.id,
                started: game.started,
                node: game.node ? game.node.identity : 'None'
            };
        });

        var nodes = this.router.getNodeStatus();

        return {
            games: games,
            nodes: nodes,
            socketCount: _.size(this.sockets),
            userCount: _.size(this.users)
        };
    }

    // Helpers
    findGameForUser(user) {
        return _.find(this.games, game => game.hasActivePlayer(user));
    }

    handshake(socket, next) {
        var versionInfo = undefined;

        if(socket.handshake.query.token && socket.handshake.query.token !== 'undefined') {
            jwt.verify(socket.handshake.query.token, this.config.secret, function(err, user) {
                if(err) {
                    logger.info(err);
                    return;
                }

                socket.request.user = user;
            });
        }

        if(socket.handshake.query.version) {
            versionInfo = moment(socket.handshake.query.version);
        }

        if(!versionInfo || versionInfo < version) {
            socket.emit('banner', 'Your client version is out of date, please refresh or clear your cache to get the latest version');
        }

        next();
    }

    // Actions
    broadcastMessage(message, ...params) {
        this.io.emit(message, ...params);
    }

    broadcastGameList(socket) {
        var gameSummaries = [];

        _.each(this.games, game => {
            gameSummaries.push(game.getSummary());
        });

        gameSummaries = _.sortBy(gameSummaries, 'createdAt').reverse();

        if(socket) {
            socket.send('games', gameSummaries);
        } else {
            this.broadcastMessage('games', gameSummaries);
        }
    }

    broadcastUserList() {
        var userList = _.map(this.users, function(user) {
            return {
                name: user.username,
                emailHash: user.emailHash
            };
        });

        this.broadcastMessage('users', userList);
    }

    sendGameState(game) {
        _.each(game.getPlayersAndSpectators(), player => {
            if(!this.sockets[player.id]) {
                logger.info('Wanted to send to ', player.id, ' but have no socket');
                return;
            }
            this.sockets[player.id].send('gamestate', game.getSummary(player.name));
        });
    }

    clearGamesForNode(nodeName) {
        _.each(this.games, game => {
            if(game.node && game.node.identity === nodeName) {
                delete this.games[game.id];
            }
        });

        this.broadcastGameList();
    }

    // Events
    onConnection(ioSocket) {
        var socket = new Socket(ioSocket, { config: this.config });

        socket.registerEvent('lobbychat', this.onLobbyChat.bind(this));
        socket.registerEvent('newgame', this.onNewGame.bind(this));
        socket.registerEvent('joingame', this.onJoinGame.bind(this));
        socket.registerEvent('leavegame', this.onLeaveGame.bind(this));
        socket.registerEvent('watchgame', this.onWatchGame.bind(this));
        socket.registerEvent('startgame', this.onStartGame.bind(this));
        socket.registerEvent('chat', this.onPendingGameChat.bind(this));
        socket.registerEvent('selectdeck', this.onSelectDeck.bind(this));

        socket.on('authenticate', this.onAuthenticated.bind(this));
        socket.on('disconnect', this.onSocketDisconnected.bind(this));

        this.sockets[ioSocket.id] = socket;

        if(socket.user) {
            this.users[socket.user.username] = socket.user;
            this.broadcastUserList();
        }

        this.messageRepository.getLastMessages().then(messages => {
            socket.send('lobbymessages', messages.reverse());
        }).catch(err => {
            logger.info(err);
        });

        this.broadcastGameList();

        if(!socket.user) {
            return;
        }

        var game = this.findGameForUser(socket.user.username);
        if(game && game.started) {
            socket.send('handoff', { address: game.node.address, port: game.node.port, protocol: game.node.protocol });
        }
    }

    onAuthenticated(user) {
        this.users[user.username] = user;

        this.broadcastUserList();
    }

    onSocketDisconnected(socket) {
        if(!socket) {
            return;
        }

        if(!socket.user) {
            delete this.sockets[socket.id];
            return;
        }

        delete this.users[socket.user.username];

        var game = this.findGameForUser(socket.user.username);
        if(!game) {
            return;
        }

        game.disconnect(socket.user.username);
        socket.send('gamestate', game.getSummary(socket.user.username));
        socket.leaveChannel(game.id);

        if(game.isEmpty()) {
            delete this.games[game.id];
        } else {
            this.sendGameState(game);
        }

        delete this.sockets[socket.id];

        this.broadcastGameList();
    }

    onNewGame(socket, gameDetails) {
        var existingGame = this.findGameForUser(socket.user.username);
        if(existingGame) {
            return;
        }

        var game = new PendingGame(socket.user, gameDetails);
        game.join(socket.id, socket.user);

        socket.joinChannel(game.id);
        this.sendGameState(game);

        this.games[game.id] = game;

        this.broadcastGameList();
    }

    onJoinGame(socket, gameId) {
        var existingGame = this.findGameForUser(socket.user.username);
        if(existingGame) {
            return;
        }

        var game = this.games[gameId];
        if(!game) {
            return;
        }

        if(game.join(socket.id, socket.user)) {
            socket.joinChannel(game.id);

            this.sendGameState(game);
        }

        this.broadcastGameList();
    }

    onStartGame(socket, gameId) {
        var game = this.games[gameId];

        if(!game || game.started) {
            return;
        }

        if(_.any(game.getPlayers(), function(player) {
            return !player.deck;
        })) {
            return;
        }

        if(!game.isOwner(socket.user.username)) {
            return;
        }

        var gameNode = this.router.startGame(game);
        if(!gameNode) {
            return;
        }

        game.node = gameNode;
        game.started = true;

        this.broadcastGameList();

        this.io.to(game.id).emit('handoff', { address: gameNode.address, port: gameNode.port, protocol: game.node.protocol });
    }

    onWatchGame(socket, gameId) {
        var existingGame = this.findGameForUser(socket.user.username);
        if(existingGame) {
            return;
        }

        var game = this.games[gameId];
        if(!game) {
            return;
        }

        if(game.watch(socket.id, socket.user)) {
            socket.joinChannel(game.id);

            if(game.started) {
                this.router.addSpectator(game, socket.user);
                socket.send('handoff', { address: game.node.address, port: game.node.port, protocol: game.node.protocol });
            } else {
                this.sendGameState(game);
            }
        }
    }

    onLeaveGame(socket) {
        var game = this.findGameForUser(socket.user.username);
        if(!game) {
            return;
        }

        game.leave(socket.user.username);
        socket.send('gamestate', game.getSummary(socket.user.username));
        socket.leaveChannel(game.id);

        if(game.isEmpty()) {
            delete this.games[game.id];
        } else {
            this.sendGameState(game);
        }

        this.broadcastGameList();
    }

    onPendingGameChat(socket, message) {
        var game = this.findGameForUser(socket.user.username);
        if(!game) {
            return;
        }

        game.chat(socket.user.username, message);
        this.sendGameState(game);
    }

    onLobbyChat(socket, message) {
        var chatMessage = { user: { username: socket.user.username, emailHash: socket.user.emailHash }, message: message, time: new Date() };

        this.messageRepository.addMessage(chatMessage);
        this.broadcastMessage('lobbychat', chatMessage);
    }

    onSelectDeck(socket, gameId, deckId) {
        if(_.isObject(deckId)) {
            deckId = deckId._id;
        }

        var game = this.games[gameId];
        if(!game) {
            return;
        }

        this.deckRepository.getById(deckId).then(deck => {
            game.selectDeck(socket.user.username, deck);

            this.sendGameState(game);
        }).catch(err => {
            logger.info(err);
        });
    }

    // router Events
    onGameClosed(gameId) {
        var game = this.games[gameId];

        if(!game) {
            return;
        }

        game.node.numGames--;

        delete this.games[gameId];

        this.broadcastGameList();
    }

    onPlayerLeft(gameId, player) {
        var game = this.games[gameId];

        if(!game) {
            return;
        }

        game.leave(player);

        if(game.isEmpty()) {
            delete this.games[gameId];
        }

        this.broadcastGameList();
    }

    onWorkerStarted(nodeName) {
        // If any games are already active on a worker with this name, then the
        // worker was probably restarted and those games are gone.
        this.clearGamesForNode(nodeName);
    }

    onWorkerTimedOut(nodeName) {
        this.clearGamesForNode(nodeName);
    }
}

module.exports = Lobby;
<commit_msg>don't send game state for a game in progress - the game node will do that<commit_after>const socketio = require('socket.io');
const Socket = require('./socket.js');
const jwt = require('jsonwebtoken');
const _ = require('underscore');
const moment = require('moment');

const logger = require('./log.js');
const version = moment(require('../version.js'));
const PendingGame = require('./pendinggame.js');
const GameRouter = require('./gamerouter.js');
const MessageRepository = require('./repositories/messageRepository.js');
const DeckRepository = require('./repositories/deckRepository.js');

class Lobby {
    constructor(server, options = {}) {
        this.sockets = {};
        this.users = {};
        this.games = {};
        this.config = options.config;
        this.messageRepository = options.messageRepository || new MessageRepository(this.config.dbPath);
        this.deckRepository = options.deckRepository || new DeckRepository(this.config.dbPath);
        this.router = options.router || new GameRouter(this.config);
        this.router.on('onGameClosed', this.onGameClosed.bind(this));
        this.router.on('onPlayerLeft', this.onPlayerLeft.bind(this));
        this.router.on('onWorkerStarted', this.onWorkerStarted.bind(this));
        this.router.on('onWorkerTimedOut', this.onWorkerTimedOut.bind(this));

        this.io = options.io || socketio(server);
        this.io.set('heartbeat timeout', 30000);
        this.io.use(this.handshake.bind(this));
        this.io.on('connection', this.onConnection.bind(this));
    }

    // External methods
    getStatus() {
        var nodeStatus = this.router.getNodeStatus();

        return nodeStatus;
    }

    disableNode(nodeName) {
        return this.router.disableNode(nodeName);
    }

    enableNode(nodeName) {
        return this.router.enableNode(nodeName);
    }

    debugDump() {
        var games = _.map(this.games, game => {
            var players = _.map(game.players, player => {
                return {
                    name: player.name,
                    left: player.left,
                    disconnected: player.disconnected,
                    id: player.id
                };
            });

            var spectators = _.map(game.spectators, spectator => {
                return {
                    name: spectator.name,
                    id: spectator.id
                };
            });

            return {
                name: game.name,
                players: players,
                spectators: spectators,
                id: game.id,
                started: game.started,
                node: game.node ? game.node.identity : 'None'
            };
        });

        var nodes = this.router.getNodeStatus();

        return {
            games: games,
            nodes: nodes,
            socketCount: _.size(this.sockets),
            userCount: _.size(this.users)
        };
    }

    // Helpers
    findGameForUser(user) {
        return _.find(this.games, game => game.hasActivePlayer(user));
    }

    handshake(socket, next) {
        var versionInfo = undefined;

        if(socket.handshake.query.token && socket.handshake.query.token !== 'undefined') {
            jwt.verify(socket.handshake.query.token, this.config.secret, function(err, user) {
                if(err) {
                    logger.info(err);
                    return;
                }

                socket.request.user = user;
            });
        }

        if(socket.handshake.query.version) {
            versionInfo = moment(socket.handshake.query.version);
        }

        if(!versionInfo || versionInfo < version) {
            socket.emit('banner', 'Your client version is out of date, please refresh or clear your cache to get the latest version');
        }

        next();
    }

    // Actions
    broadcastMessage(message, ...params) {
        this.io.emit(message, ...params);
    }

    broadcastGameList(socket) {
        var gameSummaries = [];

        _.each(this.games, game => {
            gameSummaries.push(game.getSummary());
        });

        gameSummaries = _.sortBy(gameSummaries, 'createdAt').reverse();

        if(socket) {
            socket.send('games', gameSummaries);
        } else {
            this.broadcastMessage('games', gameSummaries);
        }
    }

    broadcastUserList() {
        var userList = _.map(this.users, function(user) {
            return {
                name: user.username,
                emailHash: user.emailHash
            };
        });

        this.broadcastMessage('users', userList);
    }

    sendGameState(game) {
        if(game.started) {
            return;
        }

        _.each(game.getPlayersAndSpectators(), player => {
            if(!this.sockets[player.id]) {
                logger.info('Wanted to send to ', player.id, ' but have no socket');
                return;
            }
            this.sockets[player.id].send('gamestate', game.getSummary(player.name));
        });
    }

    clearGamesForNode(nodeName) {
        _.each(this.games, game => {
            if(game.node && game.node.identity === nodeName) {
                delete this.games[game.id];
            }
        });

        this.broadcastGameList();
    }

    // Events
    onConnection(ioSocket) {
        var socket = new Socket(ioSocket, { config: this.config });

        socket.registerEvent('lobbychat', this.onLobbyChat.bind(this));
        socket.registerEvent('newgame', this.onNewGame.bind(this));
        socket.registerEvent('joingame', this.onJoinGame.bind(this));
        socket.registerEvent('leavegame', this.onLeaveGame.bind(this));
        socket.registerEvent('watchgame', this.onWatchGame.bind(this));
        socket.registerEvent('startgame', this.onStartGame.bind(this));
        socket.registerEvent('chat', this.onPendingGameChat.bind(this));
        socket.registerEvent('selectdeck', this.onSelectDeck.bind(this));

        socket.on('authenticate', this.onAuthenticated.bind(this));
        socket.on('disconnect', this.onSocketDisconnected.bind(this));

        this.sockets[ioSocket.id] = socket;

        if(socket.user) {
            this.users[socket.user.username] = socket.user;
            this.broadcastUserList();
        }

        this.messageRepository.getLastMessages().then(messages => {
            socket.send('lobbymessages', messages.reverse());
        }).catch(err => {
            logger.info(err);
        });

        this.broadcastGameList();

        if(!socket.user) {
            return;
        }

        var game = this.findGameForUser(socket.user.username);
        if(game && game.started) {
            socket.send('handoff', { address: game.node.address, port: game.node.port, protocol: game.node.protocol });
        }
    }

    onAuthenticated(user) {
        this.users[user.username] = user;

        this.broadcastUserList();
    }

    onSocketDisconnected(socket) {
        if(!socket) {
            return;
        }

        if(!socket.user) {
            delete this.sockets[socket.id];
            return;
        }

        delete this.users[socket.user.username];

        var game = this.findGameForUser(socket.user.username);
        if(!game) {
            return;
        }

        game.disconnect(socket.user.username);
        socket.send('gamestate', game.getSummary(socket.user.username));
        socket.leaveChannel(game.id);

        if(game.isEmpty()) {
            delete this.games[game.id];
        } else {
            this.sendGameState(game);
        }

        delete this.sockets[socket.id];

        this.broadcastGameList();
    }

    onNewGame(socket, gameDetails) {
        var existingGame = this.findGameForUser(socket.user.username);
        if(existingGame) {
            return;
        }

        var game = new PendingGame(socket.user, gameDetails);
        game.join(socket.id, socket.user);

        socket.joinChannel(game.id);
        this.sendGameState(game);

        this.games[game.id] = game;

        this.broadcastGameList();
    }

    onJoinGame(socket, gameId) {
        var existingGame = this.findGameForUser(socket.user.username);
        if(existingGame) {
            return;
        }

        var game = this.games[gameId];
        if(!game) {
            return;
        }

        if(game.join(socket.id, socket.user)) {
            socket.joinChannel(game.id);

            this.sendGameState(game);
        }

        this.broadcastGameList();
    }

    onStartGame(socket, gameId) {
        var game = this.games[gameId];

        if(!game || game.started) {
            return;
        }

        if(_.any(game.getPlayers(), function(player) {
            return !player.deck;
        })) {
            return;
        }

        if(!game.isOwner(socket.user.username)) {
            return;
        }

        var gameNode = this.router.startGame(game);
        if(!gameNode) {
            return;
        }

        game.node = gameNode;
        game.started = true;

        this.broadcastGameList();

        this.io.to(game.id).emit('handoff', { address: gameNode.address, port: gameNode.port, protocol: game.node.protocol });
    }

    onWatchGame(socket, gameId) {
        var existingGame = this.findGameForUser(socket.user.username);
        if(existingGame) {
            return;
        }

        var game = this.games[gameId];
        if(!game) {
            return;
        }

        if(game.watch(socket.id, socket.user)) {
            socket.joinChannel(game.id);

            if(game.started) {
                this.router.addSpectator(game, socket.user);
                socket.send('handoff', { address: game.node.address, port: game.node.port, protocol: game.node.protocol });
            } else {
                this.sendGameState(game);
            }
        }
    }

    onLeaveGame(socket) {
        var game = this.findGameForUser(socket.user.username);
        if(!game) {
            return;
        }

        game.leave(socket.user.username);
        socket.send('gamestate', game.getSummary(socket.user.username));
        socket.leaveChannel(game.id);

        if(game.isEmpty()) {
            delete this.games[game.id];
        } else {
            this.sendGameState(game);
        }

        this.broadcastGameList();
    }

    onPendingGameChat(socket, message) {
        var game = this.findGameForUser(socket.user.username);
        if(!game) {
            return;
        }

        game.chat(socket.user.username, message);
        this.sendGameState(game);
    }

    onLobbyChat(socket, message) {
        var chatMessage = { user: { username: socket.user.username, emailHash: socket.user.emailHash }, message: message, time: new Date() };

        this.messageRepository.addMessage(chatMessage);
        this.broadcastMessage('lobbychat', chatMessage);
    }

    onSelectDeck(socket, gameId, deckId) {
        if(_.isObject(deckId)) {
            deckId = deckId._id;
        }

        var game = this.games[gameId];
        if(!game) {
            return;
        }

        this.deckRepository.getById(deckId).then(deck => {
            game.selectDeck(socket.user.username, deck);

            this.sendGameState(game);
        }).catch(err => {
            logger.info(err);
        });
    }

    // router Events
    onGameClosed(gameId) {
        var game = this.games[gameId];

        if(!game) {
            return;
        }

        game.node.numGames--;

        delete this.games[gameId];

        this.broadcastGameList();
    }

    onPlayerLeft(gameId, player) {
        var game = this.games[gameId];

        if(!game) {
            return;
        }

        game.leave(player);

        if(game.isEmpty()) {
            delete this.games[gameId];
        }

        this.broadcastGameList();
    }

    onWorkerStarted(nodeName) {
        // If any games are already active on a worker with this name, then the
        // worker was probably restarted and those games are gone.
        this.clearGamesForNode(nodeName);
    }

    onWorkerTimedOut(nodeName) {
        this.clearGamesForNode(nodeName);
    }
}

module.exports = Lobby;
<|endoftext|>


<commit_before>App.addChild('MixPanel', {
  el: 'body',

  activate: function(){
    this.VISIT_MIN_TIME = 10000;
    this.user = null;
    this.controller = this.$el.data('controller');
    this.action = this.$el.data('action');
    if(window.mixpanel){
      this.startTracking();
    }
  },

  startTracking: function(){
    var self = this;
    this.trackSelectedReward();
    this.trackPageVisit('projects', 'show', 'Visited project page');
    this.trackPageVisit('explore', 'index', 'Explore projects');
    this.trackPageLoad('contributions', 'show', 'Finished contribution');
  },

  trackPageLoad: function(controller, action, text){
    var self = this;
    this.trackOnPage(controller, action, function(){
      self.track(text);
    });
  },

  trackPageVisit: function(controller, action, text){
    var self = this;
    this.trackOnPage(controller, action, function(){
      self.trackVisit(text);
    });
  },

  trackOnPage: function(controller, action, callback){
    if(this.controller == controller && this.action == action){
      callback();
    }
  },

  identifyUser: function(){
    this.user = this.$el.data('user');
    if (this.user){
      mixpanel.name_tag(this.user.email);
      mixpanel.identify(this.user.id);
      mixpanel.people.set({
        "$email": this.user.email,
        "$created": this.user.created_at,
        "$last_login": this.user.last_sign_in_at,
        "contributions": this.user.total_contributed_projects
      });
    }
  },

  track: function(text, options){
    this.identifyUser();
    var obj             = $(this);
    var ref             = (obj.attr('href') != undefined) ? obj.attr('href') : null;
    var opt             = options || {};
    var default_options = {
      'page name':          document.title,
      'user_id':            null,
      'created':            null,
      'last_login':         null,
      'contributions':      null,
      'has_contributions':  null,
      'project':            ref,
      'url':                window.location
    };
    if(this.user){
      default_options.user_id = this.user.id;
      default_options.created = this.user.created_at;
      default_options.last_login = this.user.last_sign_in_at;
      default_options.contributions = this.user.total_contributed_projects;
      default_options.has_contributions = (this.user.total_contributed_projects > 0);
    }
    var opt     = $.fn.extend(default_options, opt);

    mixpanel.track(text, opt);
  },

  mixPanelEvent: function(target, event, text, options){
    var self = this;
    this.$(target).on(event, function(){
      self.track(text, options);
    });
  },

  trackVisit: function(eventName){
    var self = this;
    window.setTimeout(function(){
      self.track(eventName);
    }, this.VISIT_MIN_TIME);
  },

  trackSelectedReward: function(){
    this.mixPanelEvent('input#contribution_submit', 'click', 'Selected reward');
  },
});
<commit_msg>moved select reward tracking to contributions edit load<commit_after>App.addChild('MixPanel', {
  el: 'body',

  activate: function(){
    this.VISIT_MIN_TIME = 10000;
    this.user = null;
    this.controller = this.$el.data('controller');
    this.action = this.$el.data('action');
    if(window.mixpanel){
      this.startTracking();
    }
  },

  startTracking: function(){
    var self = this;
    this.trackPageVisit('projects', 'show', 'Visited project page');
    this.trackPageVisit('explore', 'index', 'Explore projects');
    this.trackPageLoad('contributions', 'show', 'Finished contribution');
    this.trackPageLoad('contributions', 'edit', 'Selected reward');
  },

  trackPageLoad: function(controller, action, text){
    var self = this;
    this.trackOnPage(controller, action, function(){
      self.track(text);
    });
  },

  trackPageVisit: function(controller, action, text){
    var self = this;
    this.trackOnPage(controller, action, function(){
      self.trackVisit(text);
    });
  },

  trackOnPage: function(controller, action, callback){
    if(this.controller == controller && this.action == action){
      callback();
    }
  },

  identifyUser: function(){
    this.user = this.$el.data('user');
    if (this.user){
      mixpanel.name_tag(this.user.email);
      mixpanel.identify(this.user.id);
      mixpanel.people.set({
        "$email": this.user.email,
        "$created": this.user.created_at,
        "$last_login": this.user.last_sign_in_at,
        "contributions": this.user.total_contributed_projects
      });
    }
  },

  track: function(text, options){
    this.identifyUser();
    var obj             = $(this);
    var ref             = (obj.attr('href') != undefined) ? obj.attr('href') : null;
    var opt             = options || {};
    var default_options = {
      'page name':          document.title,
      'user_id':            null,
      'created':            null,
      'last_login':         null,
      'contributions':      null,
      'has_contributions':  null,
      'project':            ref,
      'url':                window.location
    };
    if(this.user){
      default_options.user_id = this.user.id;
      default_options.created = this.user.created_at;
      default_options.last_login = this.user.last_sign_in_at;
      default_options.contributions = this.user.total_contributed_projects;
      default_options.has_contributions = (this.user.total_contributed_projects > 0);
    }
    var opt     = $.fn.extend(default_options, opt);

    mixpanel.track(text, opt);
  },

  mixPanelEvent: function(target, event, text, options){
    var self = this;
    this.$(target).on(event, function(){
      self.track(text, options);
    });
  },

  trackVisit: function(eventName){
    var self = this;
    window.setTimeout(function(){
      self.track(eventName);
    }, this.VISIT_MIN_TIME);
  }
});
<|endoftext|>


<commit_before>/* -*- coding: utf-8 -*- */
/* global cforum, t, uconf, MathJax, Mustache */

cforum.messages = {
  initMarkdown: function(elem_id) {
    var elem = $("#" + elem_id);

    if(elem.length) {
      cforum.markdown_buttons.l10n();

      elem.markdown({autofocus: false, savable: false, iconlibrary: 'fa',
                     language: 'de', hiddenButtons: 'cmdPreview',
                     disabledButtons: 'cmdPreview', resize: 'both',
                     additionalButtons: [
                       {
                         name: 'groupCustom',
                         data: [
                           cforum.markdown_buttons.noMarkdown,
                           cforum.markdown_buttons.tab,
                           cforum.markdown_buttons.hellip,
                           cforum.markdown_buttons.mdash,
                           cforum.markdown_buttons.almostEqualTo,
                           cforum.markdown_buttons.unequal,
                           cforum.markdown_buttons.times,
                           cforum.markdown_buttons.arrowRight,
                           cforum.markdown_buttons.arrowUp,
                           cforum.markdown_buttons.blackUpPointingTriangle,
                           cforum.markdown_buttons.rightwardsDoubleArrow,
                           cforum.markdown_buttons.trademark,
                           cforum.markdown_buttons.doublePunctuationMarks,
                           cforum.markdown_buttons.singlePunctuationMarks
                         ]
                       }
                     ]});

      var usg = $('#forum-usage');
      if(usg.length > 0) {
        elem.prev().append(usg);
      }
    }
  },

  maxLen: function(text) {
    var newlines = 0;

    for(var i = 0; i < text.length; ++i) {
      if(text.charAt(i) == "\n") {
        newlines++;

        if(newlines >= 3 && i >= 100) {
          return text.substr(0, i);
        }
      }

      if(i > 300) {
        return text.substr(0, i);
      }
    }

    return null;
  },

  initQuotes: function() {
    if(uconf('fold_quotes') != 'yes') {
      return;
    }

    $(".posting-content blockquote").each(function() {
      var $this = $(this);
      var txt = cforum.messages.maxLen($this.text());

      if(txt) {
        $this.css('display', 'none');
        $this.before('

\n' + t('unfold_quote') + '
');
        $this.prev().prepend(document.createTextNode(txt));
      }
    });

    $(".posting-content").click(function(ev) {
      var trg = $(ev.target);
      if(trg.hasClass('unfold')) {
        trg.parent().next().fadeIn('fast');
        trg.parent().remove();
      }
    });
  },

  show: function() {
    if(uconf('fold_read_nested') == 'yes' && !cforum.viewAll) {
      var nodes = $(".thread-nested:not(.archived) .message.visited:not(.active)").closest('.posting-nested');
      nodes.addClass('folded');

      if(nodes.length > 0) {
        $('body').append('
Alle ausklappen
');
        $("#unfold-all").on('click', function() {
          $('.posting-nested.folded').removeClass('folded');
          $(this).remove();
        });
      }

      $(".thread-nested").on('click', function(ev) {
        var trg = $(ev.target);

        if(!trg.is(".posting-nested")) {
          trg = trg.closest('.posting-nested');
        }

        if(trg.hasClass('folded')) {
          ev.preventDefault();
          trg.removeClass('folded');

          if($(".posting-nested.folded").length <= 0) {
            $("#unfold-all").remove();
          }
        }
      });
    }

    if($('body').hasClass('nested-view') && history.pushState) {
      $('.root a').on('click', function(event) {
        var $this = $(this);

        if($this.attr('href').match(/\/\w+\/\d+\/\w+\/\d+\/[^\/]+\/(\d+)#m\d+/)) {
          var mid = RegExp.$1;

          if(document.location.href.match(/\/\w+\/\d+\/\w+\/\d+\/[^\/]+\/(\d+)#m\d+/)) {
            var old_mid = RegExp.$1;

            if(old_mid != mid) {
              var new_url = document.location.href.replace(/\d+#m\d+/, mid + "#m" + mid);
              event.preventDefault();
              history.pushState(mid, "", new_url);

              if(uconf('fold_read_nested') == 'yes' && !cforum.viewAll) {
                $("#m" + old_mid).closest('.posting-nested').addClass('folded');
                $("#m" + mid).closest('.posting-nested').removeClass('folded');
              }

              $("html,body").scrollTop($("#m" + mid).offset().top);
            }
          }
        }
      });
    }
  },

  init: function() {
    var action = $('body').attr('data-action');
    cforum.tags.initTags();
    cforum.messages.initQuotes();

    if(action != 'create' && action != 'update') {
      cforum.messages.initCursor();
    }

    cforum.messages.initMarkdown("message_input");
    cforum.messages.initUpload();
    cforum.messages.initPreview("message_input", "message_problematic_site");
    cforum.messages.initMaxLengthWarnings();
    $("#message_input").mentions();
  },

  new: function() {
    if(cforum.messages.quotedMessage) {
      $(".form-actions").append("<button class=\"cf-btn quote-message\">" + t('add_quote') + "</button>");
      $('.form-actions .quote-message').on('click', cforum.messages.quoteMessage);
    }
  },

  quoteMessage: function(ev) {
    ev.preventDefault();
    $("#message_input").val($("#message_input").val() + cforum.messages.quotedMessage);
    $(".form-actions .quote-message").fadeOut('fast', function() { $(this).remove(); });
  },

  initCursor: function() {
    var content = $("#message_input");
    var subj = $("#message_subject");
    var author = $("#message_author");

    cforum.cf_threads.setCursor(author, subj, content);
  },

  initUpload: function() {
    if($("#message_input").length > 0 && window.Dropzone) {
      $("#message_input").after('
' + t('upload.image_area') + '
');
      $(".image-upload").dropzone({
        createImageThumbnails: false,
        maxFilesize: cforum.imageMaxSize || 2, // default to 2mb
        filesizeBase: 1024,
        url: cforum.baseUrl + 'images.json',
        clickable: true,
        acceptedFiles: 'image/*',
        previewTemplate : '

',

        dictDefaultMessage: t('upload.dictDefaultMessage'),
        dictFallbackMessage: t("upload.dictFallbackMessage"),
        dictFallbackText: t("upload.dictFallbackText"),
        dictFileTooBig: t('upload.dictFileTooBig'),
        dictInvalidFileType: t("upload.dictInvalidFileType"),
        dictResponseError: t("upload.dictResponseError"),
        dictCancelUpload: t("upload.dictCancelUpload"),
        dictCancelUploadConfirmation: t("upload.dictCancelUploadConfirmation"),
        dictRemoveFile: t("upload.dictRemoveFile"),
        dictMaxFilesExceeded: t("upload.dictMaxFilesExceeded"),

        fallback: function() { },
        init: function() {
          this.on("success", function(file, rsp) {
            var $msg = $("#message_input");
            var selection = $msg.getSelection();
            var md = '![' + t('upload.alternative_text') + '](' + cforum.basePath + 'images/' + rsp.path + ')';

            var imgup = $('.image-upload');
            imgup.removeClass('loading');
            imgup.html(t('upload.image_area'));

            $msg.replaceSelection(md);
            $msg.change();

            window.setTimeout(function() {
              $msg.focus();
              $msg.setSelection(selection.start + 2, selection.start + 17);
            }, 0);
          });

          this.on('error', function(file, response) {
            var msg;
            var imgup = $('.image-upload');

            if(typeof response == 'string') {
              msg = response;
            }
            else if(response.error) {
              msg = response.error;
            }
            else {
              msg = t('internal_error');
            }

            imgup.removeClass('loading');
            imgup.html(t('upload.image_area'));
            cforum.alert.error(msg);
          });

          this.on('sending', function(file, xhr, formData) {
            var imgup = $('.image-upload');
            imgup.addClass('loading');
            imgup.html("");

            xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
          });
        }
      });
    }
  },

  previewTimeout: null,
  oldVal: null,
  oldUrl: null,
  initPreview: function(name, problematicUrlName) {
    if(uconf('live_preview') != 'yes') {
      return;
    }

    var frm = $("#" + name).closest("form");
    var btt = frm.find("[type=submit]");
    btt.on('mouseenter focus', function() { $(".thread-message.preview").addClass('active'); });
    btt.on('mouseleave blur', function() {
      if(frm.find("[type=submit]:hover").length <= 0 && !btt.is(":focus")) {
        $(".thread-message.preview").removeClass('active');
      }
    });

    cforum.messages.showPreview(name, problematicUrlName);
    $("input[name=preview]").remove();

    var f = function() {
      if(cforum.messages.previewTimeout) {
        window.clearTimeout(cforum.messages.previewTimeout);
        cforum.messages.previewTimeout = null;
      }

      cforum.messages.previewTimeout = window.setTimeout(function() {
        cforum.messages.showPreview(name, problematicUrlName);
      }, 500);
    };

    $("#" + name + ", #" + problematicUrlName).on('keyup change', f);
  },
  showPreview: function(name, problematicUrlName) {
    var val = $("#" + name).val();
    var problematicUrl = $("#" + problematicUrlName).val();

    if(cforum.messages.oldVal != val || cforum.messages.oldUrl != problematicUrl) {
      cforum.messages.oldVal = val;
      cforum.messages.oldUrl = problematicUrl;

      if(problematicUrl) {
        var elem = $(".thread-message.preview").find(".problematic-site");
        if(elem.length <= 0) {
          $("#on-the-fly-preview").before("

");
          elem = $(".thread-message.preview").find(".problematic-site");
        }
        elem.html("" + t("problematic_site") + "");
      }
      else {
        $(".thread-message.preview").find(".problematic-site").remove();
      }

      $.post(cforum.baseUrl + 'preview',
             {content: val}).
        done(function(data) {
          $("#on-the-fly-preview").html(data);

          if(typeof window.MathJax !== 'undefined') {
            MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
          }
        });
    }
  },

  initMaxLengthWarnings: function() {
    var minput = $("#message_input");
    var maxLen = minput.attr('maxlength');

    if(!minput.length) {
      return;
    }

    minput.before("

");
    var mcount = $(".message-character-count");

    var checkLength = function() {
      // we need to work on \015\012 line endings because HTTP defines
      // this as the correct line endings; Safari and Chrome, on the
      // other hand, work with natural line endings when getting the
      // value but with \015\012 when checking for maxlength firefox,
      // on the other hand, is consistent (it uses natural line
      // endings for both) but totally insane…
      var val = minput.val().replace(/\015\012|\012|\015/g, "\015\012");
      var len = val.length;

      mcount.text(len + " / " + maxLen);

      if(len >= maxLen) {
        minput.addClass('length-error').removeClass('length-warning');
        mcount.addClass('length-error').removeClass('length-warning');
      }
      else if(len >= maxLen - 300) {
        minput.addClass('length-warning').removeClass('length-error');
        mcount.addClass('length-warning').removeClass('length-error');
      }
      else {
        minput.removeClass('length-warning').removeClass('length-error');
        mcount.removeClass('length-warning').removeClass('length-error');
      }
    };

    checkLength();
    minput.on('keyup', checkLength);
  },

  twitter: {
    new: function() {
      var minput = $("#tweet_text");
      var maxLen = 140;

      if(!minput.length) {
        return;
      }

      minput.before("

");
      var mcount = $(".twitter-message-character-count");

      var cleanup = function(text) {
        text = text.replace(/\015\012|\012|\015/g, "\012");
        // this is naive but enough for a length check
        text = text.replace(/https?:\/\/[a-zA-Z0-9:\/&+_,;%.-]+/g, '');
        text = text.replace(/#[a-zA-Z0-9]+/g, "");

        return text;
      };

      var checkLength = function() {
        var val = cleanup(minput.val());
        var len = val.length;

        mcount.text(len + " / " + maxLen);

        if(len >= maxLen) {
          minput.addClass('length-error').removeClass('length-warning');
          mcount.addClass('length-error').removeClass('length-warning');
        }
        else if(len >= maxLen - 40) {
          minput.addClass('length-warning').removeClass('length-error');
          mcount.addClass('length-warning').removeClass('length-error');
        }
        else {
          minput.removeClass('length-warning').removeClass('length-error');
          mcount.removeClass('length-warning').removeClass('length-error');
        }
      };

      checkLength();
      minput.on('keyup', checkLength);
    }
  }
};

/* eof */
<commit_msg>feat: scroll vertically to message in nested view<commit_after>/* -*- coding: utf-8 -*- */
/* global cforum, t, uconf, MathJax, Mustache */

cforum.messages = {
  initMarkdown: function(elem_id) {
    var elem = $("#" + elem_id);

    if(elem.length) {
      cforum.markdown_buttons.l10n();

      elem.markdown({autofocus: false, savable: false, iconlibrary: 'fa',
                     language: 'de', hiddenButtons: 'cmdPreview',
                     disabledButtons: 'cmdPreview', resize: 'both',
                     additionalButtons: [
                       {
                         name: 'groupCustom',
                         data: [
                           cforum.markdown_buttons.noMarkdown,
                           cforum.markdown_buttons.tab,
                           cforum.markdown_buttons.hellip,
                           cforum.markdown_buttons.mdash,
                           cforum.markdown_buttons.almostEqualTo,
                           cforum.markdown_buttons.unequal,
                           cforum.markdown_buttons.times,
                           cforum.markdown_buttons.arrowRight,
                           cforum.markdown_buttons.arrowUp,
                           cforum.markdown_buttons.blackUpPointingTriangle,
                           cforum.markdown_buttons.rightwardsDoubleArrow,
                           cforum.markdown_buttons.trademark,
                           cforum.markdown_buttons.doublePunctuationMarks,
                           cforum.markdown_buttons.singlePunctuationMarks
                         ]
                       }
                     ]});

      var usg = $('#forum-usage');
      if(usg.length > 0) {
        elem.prev().append(usg);
      }
    }
  },

  maxLen: function(text) {
    var newlines = 0;

    for(var i = 0; i < text.length; ++i) {
      if(text.charAt(i) == "\n") {
        newlines++;

        if(newlines >= 3 && i >= 100) {
          return text.substr(0, i);
        }
      }

      if(i > 300) {
        return text.substr(0, i);
      }
    }

    return null;
  },

  initQuotes: function() {
    if(uconf('fold_quotes') != 'yes') {
      return;
    }

    $(".posting-content blockquote").each(function() {
      var $this = $(this);
      var txt = cforum.messages.maxLen($this.text());

      if(txt) {
        $this.css('display', 'none');
        $this.before('

\n' + t('unfold_quote') + '
');
        $this.prev().prepend(document.createTextNode(txt));
      }
    });

    $(".posting-content").click(function(ev) {
      var trg = $(ev.target);
      if(trg.hasClass('unfold')) {
        trg.parent().next().fadeIn('fast');
        trg.parent().remove();
      }
    });
  },

  show: function() {
    if($("body").hasClass("nested-view") && window.scrollTo) {
      var anchor = document.location.hash;
      if(anchor) {
        var offset = $(anchor).offset();
        window.scrollTo(offset.left, offset.top);
      }
    }

    if(uconf('fold_read_nested') == 'yes' && !cforum.viewAll) {
      var nodes = $(".thread-nested:not(.archived) .message.visited:not(.active)").closest('.posting-nested');
      nodes.addClass('folded');

      if(nodes.length > 0) {
        $('body').append('
Alle ausklappen
');
        $("#unfold-all").on('click', function() {
          $('.posting-nested.folded').removeClass('folded');
          $(this).remove();
        });
      }

      $(".thread-nested").on('click', function(ev) {
        var trg = $(ev.target);

        if(!trg.is(".posting-nested")) {
          trg = trg.closest('.posting-nested');
        }

        if(trg.hasClass('folded')) {
          ev.preventDefault();
          trg.removeClass('folded');

          if($(".posting-nested.folded").length <= 0) {
            $("#unfold-all").remove();
          }
        }
      });
    }

    if($('body').hasClass('nested-view') && history.pushState) {
      $('.root a').on('click', function(event) {
        var $this = $(this);

        if($this.attr('href').match(/\/\w+\/\d+\/\w+\/\d+\/[^\/]+\/(\d+)#m\d+/)) {
          var mid = RegExp.$1;

          if(document.location.href.match(/\/\w+\/\d+\/\w+\/\d+\/[^\/]+\/(\d+)#m\d+/)) {
            var old_mid = RegExp.$1;

            if(old_mid != mid) {
              var new_url = document.location.href.replace(/\d+#m\d+/, mid + "#m" + mid);
              event.preventDefault();
              history.pushState(mid, "", new_url);

              if(uconf('fold_read_nested') == 'yes' && !cforum.viewAll) {
                $("#m" + old_mid).closest('.posting-nested').addClass('folded');
                $("#m" + mid).closest('.posting-nested').removeClass('folded');
              }

              $("html,body").scrollTop($("#m" + mid).offset().top);
            }
          }
        }
      });
    }
  },

  init: function() {
    var action = $('body').attr('data-action');
    cforum.tags.initTags();
    cforum.messages.initQuotes();

    if(action != 'create' && action != 'update') {
      cforum.messages.initCursor();
    }

    cforum.messages.initMarkdown("message_input");
    cforum.messages.initUpload();
    cforum.messages.initPreview("message_input", "message_problematic_site");
    cforum.messages.initMaxLengthWarnings();
    $("#message_input").mentions();
  },

  new: function() {
    if(cforum.messages.quotedMessage) {
      $(".form-actions").append("<button class=\"cf-btn quote-message\">" + t('add_quote') + "</button>");
      $('.form-actions .quote-message').on('click', cforum.messages.quoteMessage);
    }
  },

  quoteMessage: function(ev) {
    ev.preventDefault();
    $("#message_input").val($("#message_input").val() + cforum.messages.quotedMessage);
    $(".form-actions .quote-message").fadeOut('fast', function() { $(this).remove(); });
  },

  initCursor: function() {
    var content = $("#message_input");
    var subj = $("#message_subject");
    var author = $("#message_author");

    cforum.cf_threads.setCursor(author, subj, content);
  },

  initUpload: function() {
    if($("#message_input").length > 0 && window.Dropzone) {
      $("#message_input").after('
' + t('upload.image_area') + '
');
      $(".image-upload").dropzone({
        createImageThumbnails: false,
        maxFilesize: cforum.imageMaxSize || 2, // default to 2mb
        filesizeBase: 1024,
        url: cforum.baseUrl + 'images.json',
        clickable: true,
        acceptedFiles: 'image/*',
        previewTemplate : '

',

        dictDefaultMessage: t('upload.dictDefaultMessage'),
        dictFallbackMessage: t("upload.dictFallbackMessage"),
        dictFallbackText: t("upload.dictFallbackText"),
        dictFileTooBig: t('upload.dictFileTooBig'),
        dictInvalidFileType: t("upload.dictInvalidFileType"),
        dictResponseError: t("upload.dictResponseError"),
        dictCancelUpload: t("upload.dictCancelUpload"),
        dictCancelUploadConfirmation: t("upload.dictCancelUploadConfirmation"),
        dictRemoveFile: t("upload.dictRemoveFile"),
        dictMaxFilesExceeded: t("upload.dictMaxFilesExceeded"),

        fallback: function() { },
        init: function() {
          this.on("success", function(file, rsp) {
            var $msg = $("#message_input");
            var selection = $msg.getSelection();
            var md = '![' + t('upload.alternative_text') + '](' + cforum.basePath + 'images/' + rsp.path + ')';

            var imgup = $('.image-upload');
            imgup.removeClass('loading');
            imgup.html(t('upload.image_area'));

            $msg.replaceSelection(md);
            $msg.change();

            window.setTimeout(function() {
              $msg.focus();
              $msg.setSelection(selection.start + 2, selection.start + 17);
            }, 0);
          });

          this.on('error', function(file, response) {
            var msg;
            var imgup = $('.image-upload');

            if(typeof response == 'string') {
              msg = response;
            }
            else if(response.error) {
              msg = response.error;
            }
            else {
              msg = t('internal_error');
            }

            imgup.removeClass('loading');
            imgup.html(t('upload.image_area'));
            cforum.alert.error(msg);
          });

          this.on('sending', function(file, xhr, formData) {
            var imgup = $('.image-upload');
            imgup.addClass('loading');
            imgup.html("");

            xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
          });
        }
      });
    }
  },

  previewTimeout: null,
  oldVal: null,
  oldUrl: null,
  initPreview: function(name, problematicUrlName) {
    if(uconf('live_preview') != 'yes') {
      return;
    }

    var frm = $("#" + name).closest("form");
    var btt = frm.find("[type=submit]");
    btt.on('mouseenter focus', function() { $(".thread-message.preview").addClass('active'); });
    btt.on('mouseleave blur', function() {
      if(frm.find("[type=submit]:hover").length <= 0 && !btt.is(":focus")) {
        $(".thread-message.preview").removeClass('active');
      }
    });

    cforum.messages.showPreview(name, problematicUrlName);
    $("input[name=preview]").remove();

    var f = function() {
      if(cforum.messages.previewTimeout) {
        window.clearTimeout(cforum.messages.previewTimeout);
        cforum.messages.previewTimeout = null;
      }

      cforum.messages.previewTimeout = window.setTimeout(function() {
        cforum.messages.showPreview(name, problematicUrlName);
      }, 500);
    };

    $("#" + name + ", #" + problematicUrlName).on('keyup change', f);
  },
  showPreview: function(name, problematicUrlName) {
    var val = $("#" + name).val();
    var problematicUrl = $("#" + problematicUrlName).val();

    if(cforum.messages.oldVal != val || cforum.messages.oldUrl != problematicUrl) {
      cforum.messages.oldVal = val;
      cforum.messages.oldUrl = problematicUrl;

      if(problematicUrl) {
        var elem = $(".thread-message.preview").find(".problematic-site");
        if(elem.length <= 0) {
          $("#on-the-fly-preview").before("

");
          elem = $(".thread-message.preview").find(".problematic-site");
        }
        elem.html("" + t("problematic_site") + "");
      }
      else {
        $(".thread-message.preview").find(".problematic-site").remove();
      }

      $.post(cforum.baseUrl + 'preview',
             {content: val}).
        done(function(data) {
          $("#on-the-fly-preview").html(data);

          if(typeof window.MathJax !== 'undefined') {
            MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
          }
        });
    }
  },

  initMaxLengthWarnings: function() {
    var minput = $("#message_input");
    var maxLen = minput.attr('maxlength');

    if(!minput.length) {
      return;
    }

    minput.before("

");
    var mcount = $(".message-character-count");

    var checkLength = function() {
      // we need to work on \015\012 line endings because HTTP defines
      // this as the correct line endings; Safari and Chrome, on the
      // other hand, work with natural line endings when getting the
      // value but with \015\012 when checking for maxlength firefox,
      // on the other hand, is consistent (it uses natural line
      // endings for both) but totally insane…
      var val = minput.val().replace(/\015\012|\012|\015/g, "\015\012");
      var len = val.length;

      mcount.text(len + " / " + maxLen);

      if(len >= maxLen) {
        minput.addClass('length-error').removeClass('length-warning');
        mcount.addClass('length-error').removeClass('length-warning');
      }
      else if(len >= maxLen - 300) {
        minput.addClass('length-warning').removeClass('length-error');
        mcount.addClass('length-warning').removeClass('length-error');
      }
      else {
        minput.removeClass('length-warning').removeClass('length-error');
        mcount.removeClass('length-warning').removeClass('length-error');
      }
    };

    checkLength();
    minput.on('keyup', checkLength);
  },

  twitter: {
    new: function() {
      var minput = $("#tweet_text");
      var maxLen = 140;

      if(!minput.length) {
        return;
      }

      minput.before("

");
      var mcount = $(".twitter-message-character-count");

      var cleanup = function(text) {
        text = text.replace(/\015\012|\012|\015/g, "\012");
        // this is naive but enough for a length check
        text = text.replace(/https?:\/\/[a-zA-Z0-9:\/&+_,;%.-]+/g, '');
        text = text.replace(/#[a-zA-Z0-9]+/g, "");

        return text;
      };

      var checkLength = function() {
        var val = cleanup(minput.val());
        var len = val.length;

        mcount.text(len + " / " + maxLen);

        if(len >= maxLen) {
          minput.addClass('length-error').removeClass('length-warning');
          mcount.addClass('length-error').removeClass('length-warning');
        }
        else if(len >= maxLen - 40) {
          minput.addClass('length-warning').removeClass('length-error');
          mcount.addClass('length-warning').removeClass('length-error');
        }
        else {
          minput.removeClass('length-warning').removeClass('length-error');
          mcount.removeClass('length-warning').removeClass('length-error');
        }
      };

      checkLength();
      minput.on('keyup', checkLength);
    }
  }
};

/* eof */
<|endoftext|>


<commit_before>var http = require('http');
var httpProxy = require('http-proxy');
var https = require('https');
var fs = require('fs');
var path = require('path');
var options = {
    ca: fs.readFileSync(path.join(__dirname, '/www_yvanwang_com/www_yvanwang_com.ca-bundle')),
    key: fs.readFileSync(path.join(__dirname, '/www_yvanwang_com/www_yvanwang_com.key')),
    cert: fs.readFileSync(path.join(__dirname, '/www_yvanwang_com/www_yvanwang_com.crt')),
};

// 新建一个代理 Proxy Server 对象
var proxy = httpProxy.createProxyServer({});

// 捕获异常
proxy.on('error', function (err, req, res) {
    res.writeHead(500, {
        'Content-Type': 'text/plain'
    });
    res.end('Something went wrong. And we are reporting a custom error message.');
});

// 另外新建一个 HTTP 80 端口的服务器，也就是常规 Node 创建 HTTP 服务器的方法。
// 在每次请求中，调用 proxy.web(req, res config) 方法进行请求分发
var httpsServer = https.createServer(options, function (req, res) {
    // 在这里可以自定义你的路由分发
    var host = req.headers.host, ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    console.log("client ip:" + ip + ", host:" + host);
    switch (host) {
        case 'yvanwang.com':
            proxy.web(req, res, {target: 'http://localhost:3000'});
            break;
        case 'www.yvanwang.com':
            proxy.web(req, res, {target: 'http://localhost:3000'});
            break;
        case 'blog.yvanwang.com':
            proxy.web(req, res, {target: 'http://localhost:3000'});
            break;
        case 'sunnyhuan.yvanwang.com':
            proxy.web(req, res, {target: 'http://localhost:3000'});
            break;
        default:
            res.writeHead(200, {
                'Content-Type': 'text/plain'
            });
            res.end("Welcome to my yvanwang's blog!");
    }
});

var httpServer = http.createServer(function (req, res) {
    // 在这里可以自定义你的路由分发

});

/*httpProxy.createProxyServer({
    target: 'https://yvanwang.com',
    agent  : https.globalAgent,
    headers: {
        host: 'yvanwang.com'
    }
}).listen(8011);*/

httpProxy.createServer(function (req, res, proxy) {
    // Inspect request and decide whether to proxy, then...
    var host = req.headers.host, ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    console.log("client ip:" + ip + ", host:" + host);
    switch (host) {
        case 'yvanwang.com':
            proxy.proxyRequest(req, res, {
                target: 'https://yvanwang.com',
                agent  : https.globalAgent,
                headers: {
                    host: 'yvanwang.com'
                }
            });
            break;
        case 'www.yvanwang.com':
            proxy.web(req, res, {target: 'https://www.yvanwang.com'});
            break;
        case 'blog.yvanwang.com':
            proxy.web(req, res, {target: 'https://blog.yvanwang.com'});
            break;
        case 'sunnyhuan.yvanwang.com':
            proxy.web(req, res, {target: 'https://sunnyhuan.yvanwang.com'});
            break;
        default:
            res.writeHead(200, {
                'Content-Type': 'text/plain'
            });
            res.end("Welcome to my yvanwang's blog!");
    }
}).listen(80);

console.log("httpsServer listening on port 443");
httpsServer.listen(443);

/*console.log("httpServer listening on port 80");
httpServer.listen(80);*/
<commit_msg>fix editor bugs<commit_after>var http = require('http');
var httpProxy = require('http-proxy');
var https = require('https');
var fs = require('fs');
var path = require('path');
var options = {
    ca: fs.readFileSync(path.join(__dirname, '/www_yvanwang_com/www_yvanwang_com.ca-bundle')),
    key: fs.readFileSync(path.join(__dirname, '/www_yvanwang_com/www_yvanwang_com.key')),
    cert: fs.readFileSync(path.join(__dirname, '/www_yvanwang_com/www_yvanwang_com.crt')),
};

// 新建一个代理 Proxy Server 对象
var proxy = httpProxy.createProxyServer({});

// 捕获异常
proxy.on('error', function (err, req, res) {
    res.writeHead(500, {
        'Content-Type': 'text/plain'
    });
    res.end('Something went wrong. And we are reporting a custom error message.');
});

// 另外新建一个 HTTP 80 端口的服务器，也就是常规 Node 创建 HTTP 服务器的方法。
// 在每次请求中，调用 proxy.web(req, res config) 方法进行请求分发
var httpsServer = https.createServer(options, function (req, res) {
    // 在这里可以自定义你的路由分发
    var host = req.headers.host, ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    console.log("client ip:" + ip + ", host:" + host);
    switch (host) {
        case 'yvanwang.com':
            proxy.web(req, res, {target: 'http://localhost:3000'});
            break;
        case 'www.yvanwang.com':
            proxy.web(req, res, {target: 'http://localhost:3000'});
            break;
        case 'blog.yvanwang.com':
            proxy.web(req, res, {target: 'http://localhost:3000'});
            break;
        case 'sunnyhuan.yvanwang.com':
            proxy.web(req, res, {target: 'http://localhost:3000'});
            break;
        default:
            res.writeHead(200, {
                'Content-Type': 'text/plain'
            });
            res.end("Welcome to my yvanwang's blog!");
    }
});

/*var httpServer = http.createServer(function (req, res) {
    // 在这里可以自定义你的路由分发

});*/

httpProxy.createProxyServer({
    target: 'https://yvanwang.com',
    agent  : https.globalAgent,
    headers: {
        host: 'yvanwang.com'
    }
}).listen(80);

/*httpProxy.createServer(function (req, res, proxy) {
    // Inspect request and decide whether to proxy, then...
    var host = req.headers.host, ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    console.log("client ip:" + ip + ", host:" + host);
    switch (host) {
        case 'yvanwang.com':
            proxy.proxyRequest(req, res, {
                target: 'https://yvanwang.com',
                agent  : https.globalAgent,
                headers: {
                    host: 'yvanwang.com'
                }
            });
            break;
        case 'www.yvanwang.com':
            proxy.web(req, res, {target: 'https://www.yvanwang.com'});
            break;
        case 'blog.yvanwang.com':
            proxy.web(req, res, {target: 'https://blog.yvanwang.com'});
            break;
        case 'sunnyhuan.yvanwang.com':
            proxy.web(req, res, {target: 'https://sunnyhuan.yvanwang.com'});
            break;
        default:
            res.writeHead(200, {
                'Content-Type': 'text/plain'
            });
            res.end("Welcome to my yvanwang's blog!");
    }
}).listen(80);*/

console.log("httpsServer listening on port 443");
httpsServer.listen(443);

/*console.log("httpServer listening on port 80");
httpServer.listen(80);*/
<|endoftext|>


<commit_before>/* Clizia  - A Comedy written by Machiavelli */

//It's funny, because it's javascript

var Clizia = {};

Array.prototype.max = function() { return Math.max.apply(null, this); };
Array.prototype.min = function() { return Math.min.apply(null, this); };

/* Proper validation of an array requires a lot of checks */
var is_array = function (value) {
        return value &&
                typeof value === 'object' &&
                typeof value.length === 'number' &&
                typeof value.splice === 'function' &&
                !(value.propertyIsEnumerable('length'));
};

Clizia.Graph = function(args) {
        var that = {};

        that.init = function(args) {
		if (!args) throw "Clizia.Graph requires at least some settings. You have provided none."

                if (!args.chart) throw "Clizia.Graph needs a chart";
                that.chart = args.chart;

                if (!args.metric) throw "Clizia.Graph needs a metric"
                that.metric = args.metric;
        }

        that.render = function(args) { throw "Cannot invoke parent Clizia.Graph.render() directly." }
        that.update = function(args) { throw "Cannot invoke parent Clizia.Graph.update() directly." }

	next_color = function() {
		if (typeof clizia_palette === "undefined") {
			clizia_palette = new Rickshaw.Color.Palette({scheme: "munin"})
		}
		return clizia_palette.color()
	}

	that.state = function(args) {
		if (typeof args === "String" ) { args = {state: args} }

		function rmv_wait() { graph.find(".waiting").remove() }

		if (args.state) {
			var graph = $("#"+that.chart)
			if (args.state === "waiting") {
				graph.append("

")
			} else if (args.state === "error") {
				rmv_wait()

				error = args.error;
				removeURL = args.removeURL || ""
				showURL = args.showURL || ""
				detail = args.detail || ""

				error = stripHTML(error);
				error_alert = "
" + error;

				if (showURL) {
					error_alert +=  ". Check source";
				}

				if (removeURL) {
					error_alert +=  ". Remove graph.";
				}

				if (detail) {
					error_alert +=  "(details)" +
						"
" +
						detail +
						"
";
				}
				error_alert += "
";
				graph.append(error_alert)

				graph.addClass("error")
			} else if (args.state === "complete") {
				rmv_wait()
			}
		} else {
			throw "No state"
		}
	}
	function stripHTML(e) {  return e.replace(/<(?:.|\n)*?>/gm, '').replace(/(\r\n|\n|\r)/gm,""); }

	that.metric_complete = function() {
		if (typeof nanobar === "object") {
			nanobar.inc()
		}
	}

	that.metric_failed = function() {
		if (typeof nanobar === "object") {
			nanobar.complete()
		}
	}

	that.init(args);
	return that;
}

<commit_msg>When erroring, mock-click the stop button, since we can't update an errored graph<commit_after>/* Clizia  - A Comedy written by Machiavelli */

//It's funny, because it's javascript

var Clizia = {};

Array.prototype.max = function() { return Math.max.apply(null, this); };
Array.prototype.min = function() { return Math.min.apply(null, this); };

/* Proper validation of an array requires a lot of checks */
var is_array = function (value) {
        return value &&
                typeof value === 'object' &&
                typeof value.length === 'number' &&
                typeof value.splice === 'function' &&
                !(value.propertyIsEnumerable('length'));
};

Clizia.Graph = function(args) {
        var that = {};

        that.init = function(args) {
		if (!args) throw "Clizia.Graph requires at least some settings. You have provided none."

                if (!args.chart) throw "Clizia.Graph needs a chart";
                that.chart = args.chart;

                if (!args.metric) throw "Clizia.Graph needs a metric"
                that.metric = args.metric;
        }

        that.render = function(args) { throw "Cannot invoke parent Clizia.Graph.render() directly." }
        that.update = function(args) { throw "Cannot invoke parent Clizia.Graph.update() directly." }

	next_color = function() {
		if (typeof clizia_palette === "undefined") {
			clizia_palette = new Rickshaw.Color.Palette({scheme: "munin"})
		}
		return clizia_palette.color()
	}

	that.state = function(args) {
		if (typeof args === "String" ) { args = {state: args} }

		function rmv_wait() { graph.find(".waiting").remove() }

		if (args.state) {
			var graph = $("#"+that.chart)
			if (args.state === "waiting") {
				graph.append("

")
			} else if (args.state === "error") {
				rmv_wait()

				error = args.error;
				removeURL = args.removeURL || ""
				showURL = args.showURL || ""
				detail = args.detail || ""

				error = stripHTML(error);
				error_alert = "
" + error;

				if (showURL) {
					error_alert +=  ". Check source";
				}

				if (removeURL) {
					error_alert +=  ". Remove graph.";
				}

				if (detail) {
					error_alert +=  "(details)" +
						"
" +
						detail +
						"
";
				}
				error_alert += "
";
				graph.append(error_alert)

				stopButtonClick()

				graph.addClass("error")
			} else if (args.state === "complete") {
				rmv_wait()
			}
		} else {
			throw "No state"
		}
	}
	function stripHTML(e) {  return e.replace(/<(?:.|\n)*?>/gm, '').replace(/(\r\n|\n|\r)/gm,""); }

	that.metric_complete = function() {
		if (typeof nanobar === "object") {
			nanobar.inc()
		}
	}

	that.metric_failed = function() {
		if (typeof nanobar === "object") {
			nanobar.complete()
		}
	}

	that.init(args);
	return that;
}

<|endoftext|>


<commit_before>var fs = Npm.require('fs')
var id3js = Meteor.npmRequire('id3js')
var files = fs.readdirSync('../../../../../server/songs/')
console.log(id3js)
Meteor.startup(function () {

  if (Contacts.find({}).count() < 15) {
    _(3).times(function(n) {
      var user = Fake.user();
	  var tracksArr = files
	  var tracksObj = []
	  tracksArr.forEach(function(track){
		  obj = {
			  track: track,
			  artist: 'artist'
		  }
		  tracksObj.push(obj)
	  })
	  var track = tracksArr[0]
	  id3js({ file: '../../../../../server/songs/' + track, type: id3js.OPEN_LOCAL }, function(err, tags) {
		  console.log('tags', tags.title)
		  console.log('tags', tags.artist)
	      // tags now contains your ID3 tags
	})

      Contacts.insert({
        name: {
          first: tracksObj[n].track,
          last: tracksObj[n].artist
        },
        emails: [{label: 'Work', address: user.email}],
        priority: Fake.fromArray(['High', 'Medium', 'Low']),
        location: {
          city: Fake.word()
        },
        details: {
		  tags: '',
          notes: Fake.paragraph(),
          active: Fake.fromArray([true, false])
        }
      });
    });
  }

});
<commit_msg>add track<commit_after>var fs = Npm.require('fs')
var id3js = Meteor.npmRequire('id3js')
var files = fs.readdirSync('../../../../../server/songs/')
console.log(id3js)
Meteor.startup(function () {

  if (Contacts.find({}).count() < 55) {
    _(1).times(function(n) {
      var user = Fake.user();
	  var tracksArr = files
	  var tracksObj = []
	  console.log('foo')
	  tracksArr.forEach(function(track){
		  id3js({ file: '../../../../../server/songs/' + track, type: id3js.OPEN_LOCAL }, addTrack)
	  })

	  function addTrack(err, tags) {
		  console.log('err, tags', err, tags)
		  Contacts.insert({
			name: {
			  first: 'tracksObj[n].title',
			  last:'tracksObj[n].artist'
			},
			emails: [{label: 'Work', address: user.email}],
			priority: Fake.fromArray(['High', 'Medium', 'Low']),
			location: {
			  city: Fake.word()
			},
			details: {
			  tags: '',
			  notes: Fake.paragraph(),
			  active: Fake.fromArray([true, false])
			}
		  });
	  }
    });
  }

});
<|endoftext|>


<commit_before>$(document).on('turbolinks:load', function() {
  var $input = $('input#gobierto_search');
  var $resultsContainer = $('#search_results');

  function truncateOnWord(str, limit) {
    var trimmable = '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF';
    var reg = new RegExp('(?=[' + trimmable + '])');
    var words = str.split(reg);
    var count = 0;
    return words.filter(function(word) {
      count += word.length;
      return count <= limit;
    }).join('');
  }

  function itemUpdatedAt(d){
    if(d['updated_at'] === undefined)
      return;

    return I18n.t("layouts.search.updated") + ": " + I18n.l("date.formats.short", d['updated_at']);
  }

  function itemDescription(d){
    var maxLenght = 200;
    var description = (d['bio'] || d['description'] || d['body'] || d['bio_' + I18n.locale] || d['description_' + I18n.locale] || d['body_' + I18n.locale] || '');
    if(description === '' || description.length < maxLenght)
      return description;
    return truncateOnWord(description, maxLenght) + '...';
  }

  function itemType(d){
    if(d['class_name'] === undefined)
      return;

    switch(d['class_name']){
      case 'GobiertoPeople::Person':
        return I18n.t("layouts.search.person_item");
      case 'GobiertoPeople::PersonPost':
        return I18n.t("layouts.search.person_post_item");
      case 'GobiertoCalendars::Event':
        return I18n.t("layouts.search.event_item");
      case 'GobiertoPeople::PersonStatement':
        return I18n.t("layouts.search.person_statement_item");
      case 'GobiertoCms::Page':
        return I18n.t("layouts.search.page_item");
      case 'GobiertoBudgets::BudgetLine':
        if (d['kind'] === 'I') {
          return I18n.t("layouts.search.budget_line_item_income");
        } else {
          return I18n.t("layouts.search.budget_line_item_expense");
        }
    }
  }

  function searchCallback(err, content) {
    if (err) {
      console.error(err);
      return;
    }

    $resultsContainer.html('');
    var sum = 0;
    content.results.forEach(function(indexResults){
      sum += indexResults.nbHits;
    });
    if(sum > 0) {
      content.results.forEach(function(indexResults){
        indexResults.hits.forEach(function(d){
          if(true == true) {
            var result = '
' +
              '
' + '<a class="tipsit" ' +
              (d['title'] || d['name'] || d['title_' + I18n.locale] || d['name_' + I18n.locale]) +
              '' + itemDescription(d) + ''  +
              '' + '
' +
              '
' + itemUpdatedAt(d) + '
' +
            '
';
          } else {
            var result = '<a class="result" ' +
              '
' + (d['title'] || d['name'] || d['title_' + I18n.locale] || d['name_' + I18n.locale]) + '
' +
              '
' +
                '
' + itemDescription(d) + '
' +
                '' + itemType(d) + '' +
                (itemUpdatedAt(d) ? ' · ' + itemUpdatedAt(d) + '' : '') +
              '
' +
            '';
          }

          var div = $(result);
          div.appendTo($resultsContainer);
        });
      });
    } else {
      $('

'+I18n.t("layouts.search.no_results")+'

').appendTo($resultsContainer);
    }

    $('
'+I18n.t("layouts.search.powered_by")+'
').appendTo($resultsContainer);
  }

  $input.on('keyup', function(e){
    var q = $(this).val();
    var queries = [];
    window.searchClient.indexes.forEach(function(index){
      queries.push({
        indexName: index,
        query: q,
        params: {
          hitsPerPage: 10,
          filters: window.searchClient.filters
        }
      });
    });
    if(q.length > 2){
       if(window.location.href.includes("admin")){
         window.searchClient.client.search(queries, searchCallback);
       } else {
         window.searchClient.client.search(queries, searchCallback);
       }
    } else {
      $resultsContainer.html('');
    }
  });

  $input.focusin(function() {
    $resultsContainer.show();
  });

  // Hide resultsContainer if clicked outside the search input and results
  $('body').click(function(e) {
    if ( !$(e.target).is('#search_results *') && !$(e.target).is('input#gobierto_search') ) {
      $resultsContainer.hide();
    }
  });
});
<commit_msg>Distinc html for front and back searches<commit_after>$(document).on('turbolinks:load', function() {
  var $input = $('input#gobierto_search');
  var $resultsContainer = $('#search_results');

  function truncateOnWord(str, limit) {
    var trimmable = '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF';
    var reg = new RegExp('(?=[' + trimmable + '])');
    var words = str.split(reg);
    var count = 0;
    return words.filter(function(word) {
      count += word.length;
      return count <= limit;
    }).join('');
  }

  function itemUpdatedAt(d){
    if(d['updated_at'] === undefined)
      return;

    return I18n.t("layouts.search.updated") + ": " + I18n.l("date.formats.short", d['updated_at']);
  }

  function itemDescription(d){
    var maxLenght = 200;
    var description = (d['bio'] || d['description'] || d['body'] || d['bio_' + I18n.locale] || d['description_' + I18n.locale] || d['body_' + I18n.locale] || '');
    if(description === '' || description.length < maxLenght)
      return description;
    return truncateOnWord(description, maxLenght) + '...';
  }

  function itemType(d){
    if(d['class_name'] === undefined)
      return;

    switch(d['class_name']){
      case 'GobiertoPeople::Person':
        return I18n.t("layouts.search.person_item");
      case 'GobiertoPeople::PersonPost':
        return I18n.t("layouts.search.person_post_item");
      case 'GobiertoCalendars::Event':
        return I18n.t("layouts.search.event_item");
      case 'GobiertoPeople::PersonStatement':
        return I18n.t("layouts.search.person_statement_item");
      case 'GobiertoCms::Page':
        return I18n.t("layouts.search.page_item");
      case 'GobiertoBudgets::BudgetLine':
        if (d['kind'] === 'I') {
          return I18n.t("layouts.search.budget_line_item_income");
        } else {
          return I18n.t("layouts.search.budget_line_item_expense");
        }
    }
  }

  function searchCallback(err, content) {
    if (err) {
      console.error(err);
      return;
    }

    $resultsContainer.html('');
    var sum = 0;
    content.results.forEach(function(indexResults){
      sum += indexResults.nbHits;
    });
    if(sum > 0) {
      content.results.forEach(function(indexResults){
        indexResults.hits.forEach(function(d){
          if(window.searchClient.indexes.length == 1) {
            var result = '
' +
              '
' + '<a class="tipsit" ' +
              (d['title'] || d['name'] || d['title_' + I18n.locale] || d['name_' + I18n.locale]) +
              '' + itemDescription(d) + ''  +
              '' + '
' +
              '
' + itemUpdatedAt(d) + '
' +
            '
';
          } else {
            var result = '<a class="result" ' +
              '
' + (d['title'] || d['name'] || d['title_' + I18n.locale] || d['name_' + I18n.locale]) + '
' +
              '
' +
                '
' + itemDescription(d) + '
' +
                '' + itemType(d) + '' +
                (itemUpdatedAt(d) ? ' · ' + itemUpdatedAt(d) + '' : '') +
              '
' +
            '';
          }

          var div = $(result);
          div.appendTo($resultsContainer);
        });
      });
    } else {
      $('

'+I18n.t("layouts.search.no_results")+'

').appendTo($resultsContainer);
    }

    $('
'+I18n.t("layouts.search.powered_by")+'
').appendTo($resultsContainer);
  }

  $input.on('keyup', function(e){
    var q = $(this).val();
    var queries = [];
    window.searchClient.indexes.forEach(function(index){
      queries.push({
        indexName: index,
        query: q,
        params: {
          hitsPerPage: 10,
          filters: window.searchClient.filters
        }
      });
    });
    if(q.length > 2){
       if(window.location.href.includes("admin")){
         window.searchClient.client.search(queries, searchCallback);
       } else {
         window.searchClient.client.search(queries, searchCallback);
       }
    } else {
      $resultsContainer.html('');
    }
  });

  $input.focusin(function() {
    $resultsContainer.show();
  });

  // Hide resultsContainer if clicked outside the search input and results
  $('body').click(function(e) {
    if ( !$(e.target).is('#search_results *') && !$(e.target).is('input#gobierto_search') ) {
      $resultsContainer.hide();
    }
  });
});
<|endoftext|>