<commit_before>const domReady = function (callback) {
	const state = document.readyState;
	if (state === 'interactive' || state === 'complete') {
		callback();
	} else {
		document.addEventListener('DOMContentLoaded', callback);
	}
};

domReady(() => {
	const projectname = document.createElement('a');
	projectname.classList.add('project-name');
	projectname.text = 'aureooms/js-prime';
	projectname.href = './index.html';

	const header = document.querySelectorAll('header')[0];
	header.insertBefore(projectname, header.firstChild);

	const testlink = document.querySelector('header > a[data-ice="testLink"]');
	testlink.href = 'https://coveralls.io/github/aureooms/js-prime';
	testlink.target = '_BLANK';

	const searchBox = document.querySelector('.search-box');
	const input = document.querySelector('.search-input');

	// Active search box when focus on searchBox.
	input.addEventListener('focus', () => {
		searchBox.classList.add('active');
	});
});
<commit_msg>:robot: docs: Simplify scripts.<commit_after>const domReady = function (callback) {
	const state = document.readyState;
	if (state === 'interactive' || state === 'complete') {
		callback();
	} else {
		document.addEventListener('DOMContentLoaded', callback);
	}
};

domReady(() => {
	const projectname = document.createElement('a');
	projectname.classList.add('project-name');
	projectname.text = 'aureooms/js-prime';
	projectname.href = './index.html';

	const header = document.querySelector('header');
	header.insertBefore(projectname, header.firstChild);

	const testlink = document.querySelector('header > a[data-ice="testLink"]');
	testlink.href = 'https://coveralls.io/github/aureooms/js-prime';
	testlink.target = '_BLANK';

	const searchBox = document.querySelector('.search-box');
	const input = document.querySelector('.search-input');

	// Active search box when focus on searchBox.
	input.addEventListener('focus', () => {
		searchBox.classList.add('active');
	});
});
<|endoftext|>


<commit_before>import passport from 'passport'

import {application, NxusModule} from 'nxus-core'
import {router} from 'nxus-router'
import {mailer} from 'nxus-mailer'
import {templater} from 'nxus-templater'
import {users} from '../../'
import HasUserModel from '../../HasUserModel'

export default class UsersLoginRoutes extends HasUserModel {
  constructor() {
    super()

    templater.default().template(__dirname+"/../../../templates/user-login.ejs", "bare")
    templater.default().template(__dirname+"/../../../templates/user-forgot-email.ejs")
    templater.default().template(__dirname+"/../../../templates/user-forgot-password.ejs")

    users.getBaseUrl().then((baseUrl) => {
      this.baseUrl = baseUrl
      router.route('GET', this.baseUrl+'logout', ::this._logoutHandler)
      router.route('POST', this.baseUrl+'forgot', ::this._forgotSaveHandler)
      router.route('GET', this.baseUrl+'login-link', ::this._loginLinkHandler)
      router.route('GET', this.baseUrl+'login', ::this._loginPageHandler)
      router.route('GET', this.baseUrl+'forgot', ::this._forgotHandler)
      router.route('POST', this.baseUrl+'login', [::this._authenticationCallback, ::this._loginHandler])
    })
  }

  _loginPageHandler(req, res) {
    return templater.render('user-login', {req, user: req.user, redirect: req.param('redirect')}).then(::res.send)
  }

  _forgotHandler(req, res) {
    return templater.render('user-forgot-password', {req, user: req.user}).then(::res.send)
  }

  _forgotSaveHandler(req, res) {
    var email = req.param('email')
    return this.models.User.findOne({email}).then((user) => {
      if(!user) throw new Error('No user matching that email was found.')
      var link = "http://"+application.config.baseUrl+this.baseUrl+"login-link?token="+user.resetPasswordToken
      return templater.render('user-forgot-email', {user, email, link, siteName: application.config.siteName})
    }).then((content) => {
      let fromEmail = (application.config.users && application.config.users.forgotPasswordEmail) ? application.config.users.forgotPasswordEmail : "noreply@"+((application.config.mailer && application.config.mailer.emailDomain) || application.config.host)
      return mailer.send(email, fromEmail, "Password recovery", content, {html: content})
    }).then(() => {
      req.flash('info', 'An email has been sent to the address you provided.');
      res.redirect(this.baseUrl+'login');
    }).catch((e) => {
      req.flash('error', e)
      res.redirect(this.baseUrl+'login');
    });
  }

  _loginLinkHandler(req, res) {
    var resetPasswordToken = req.param('token')
    return this.models.User.findOne({resetPasswordToken}).then((user) => {
      if(!user) throw new Error('No user found')
      req.login(user, () => {res.redirect(this.baseUrl+'profile')})
    }).catch(() => {
      res.redirect(this.baseUrl+'login')
    })
  }

  _authenticationCallback(req, res, next) {
    let failureRedirect = this.baseUrl+'login'
    if (req.param.redirect) {
      failureRedirect += '?redirect=' + encodeURIComponent(req.param('redirect'))
    }
    passport.authenticate('local', {failureRedirect: failureRedirect, failureFlash: true})(req, res, next)
  }

  _loginHandler(req, res) {
    req.session.flash = []
    req.session.save(err => {
      var r = req.param('redirect')
      if (r) {
        return res.redirect(r)
      } else
        return res.redirect("/")
    })
  }

  _logoutHandler(req, res) {
    req.session.flash = []
    req.session.save(err => {
      req.logout()
      if(req.params.redirect)
        res.redirect(req.params.redirect)
      else
        res.redirect(this.baseUrl+'login')
    })
  }
}
<commit_msg>Added better logging for forgot passwor link issues<commit_after>import passport from 'passport'

import {application, NxusModule} from 'nxus-core'
import {router} from 'nxus-router'
import {mailer} from 'nxus-mailer'
import {templater} from 'nxus-templater'
import {users} from '../../'
import HasUserModel from '../../HasUserModel'

export default class UsersLoginRoutes extends HasUserModel {
  constructor() {
    super()

    templater.default().template(__dirname+"/../../../templates/user-login.ejs", "bare")
    templater.default().template(__dirname+"/../../../templates/user-forgot-email.ejs")
    templater.default().template(__dirname+"/../../../templates/user-forgot-password.ejs")

    users.getBaseUrl().then((baseUrl) => {
      this.baseUrl = baseUrl
      router.route('GET', this.baseUrl+'logout', ::this._logoutHandler)
      router.route('POST', this.baseUrl+'forgot', ::this._forgotSaveHandler)
      router.route('GET', this.baseUrl+'login-link', ::this._loginLinkHandler)
      router.route('GET', this.baseUrl+'login', ::this._loginPageHandler)
      router.route('GET', this.baseUrl+'forgot', ::this._forgotHandler)
      router.route('POST', this.baseUrl+'login', [::this._authenticationCallback, ::this._loginHandler])
    })
  }

  _loginPageHandler(req, res) {
    return templater.render('user-login', {req, user: req.user, redirect: req.param('redirect')}).then(::res.send)
  }

  _forgotHandler(req, res) {
    return templater.render('user-forgot-password', {req, user: req.user}).then(::res.send)
  }

  _forgotSaveHandler(req, res) {
    var email = req.param('email')
    return this.models.User.findOne({email}).then((user) => {
      if(!user) throw new Error('No user matching that email was found.')
      var link = "http://"+application.config.baseUrl+this.baseUrl+"login-link?token="+user.resetPasswordToken
      return templater.render('user-forgot-email', {user, email, link, siteName: application.config.siteName})
    }).then((content) => {
      let fromEmail = (application.config.users && application.config.users.forgotPasswordEmail) ? application.config.users.forgotPasswordEmail : "noreply@"+((application.config.mailer && application.config.mailer.emailDomain) || application.config.host)
      return mailer.send(email, fromEmail, "Password recovery", content, {html: content})
    }).then(() => {
      req.flash('info', 'An email has been sent to the address you provided.');
      res.redirect(this.baseUrl+'login');
    }).catch((e) => {
      this.log.error('Error sending forgot password email', e)
      req.flash('error', e.message ? e.message : e)
      res.redirect(this.baseUrl+'forgot');
    });
  }

  _loginLinkHandler(req, res) {
    var resetPasswordToken = req.param('token')
    return this.models.User.findOne({resetPasswordToken}).then((user) => {
      if(!user) throw new Error('No user found')
      req.login(user, () => {res.redirect(this.baseUrl+'profile')})
    }).catch(() => {
      res.redirect(this.baseUrl+'login')
    })
  }

  _authenticationCallback(req, res, next) {
    let failureRedirect = this.baseUrl+'login'
    if (req.param.redirect) {
      failureRedirect += '?redirect=' + encodeURIComponent(req.param('redirect'))
    }
    passport.authenticate('local', {failureRedirect: failureRedirect, failureFlash: true})(req, res, next)
  }

  _loginHandler(req, res) {
    req.session.flash = []
    req.session.save(err => {
      var r = req.param('redirect')
      if (r) {
        return res.redirect(r)
      } else
        return res.redirect("/")
    })
  }

  _logoutHandler(req, res) {
    req.session.flash = []
    req.session.save(err => {
      req.logout()
      if(req.params.redirect)
        res.redirect(req.params.redirect)
      else
        res.redirect(this.baseUrl+'login')
    })
  }
}
<|endoftext|>


<commit_before>var Insert = require('./commands').Insert
  , Update = require('./commands').Update
  , Remove = require('./commands').Remove
  , Query = require('../connection/commands').Query
  , copy = require('../connection/utils').copy
  , CommandResult = require('../topologies/command_result')
  , MongoError = require('../error');

var LegacySupport = function() {

  //
  // Aggregate up all the results
  //
  var aggregateInsertResults = function(results, connection) {
    var final = { ok: 1, n: 0 }

    // Map all the results coming back
    for(var i = 0; i < results.length; i++) {
      var result = results[i];

      // We have a command error
      if(result != null && result.ok == 0 || result.err || result.errmsg) {
        if(result.ok == 0) final.ok = 0;
        final.code = result.code;
        final.errmsg = result.errmsg || result.err || result.errMsg;

        // Check if we have a write error
        if(result.code == 11000
          || result.code == 11001
          || result.code == 12582
          || result.code == 14
          || result.code == 13511) {
          if(final.writeErrors == null) final.writeErrors = [];
          final.writeErrors.push({
              index: i
            , code: result.code
            , errmsg: result.errmsg || result.err || result.errMsg
          });
        } else {
          final.writeConcernError = {
              code: result.code
            , errmsg: result.errmsg || result.err || result.errMsg
          }
        }
      } else {
        final.n += 1;
      }

      // Result as expected
      if(result != null && result.lastOp) final.lastOp = result.lastOp;
    }

    // Return final aggregated results
    return new CommandResult(final, connection);
  }

  //
  // Execute all inserts in an ordered manner
  //
  var executeOrdered = function(command, ismaster, ns, bson, pool, callbacks, ops, options, callback) {
    var _ops = ops.slice(0);
    // Bind to current domain
    callback = bindToCurrentDomain(callback);
    // Collect all the getLastErrors
    var getLastErrors = [];

    // Execute an operation
    var executeOp = function(list, _callback) {
      // Get a pool connection
      var connection = pool.get();
      // No more items in the list
      if(list.length == 0) return _callback(null, aggregateInsertResults(getLastErrors, connection));

      // Get the first operation
      var doc = list.shift();

      // Create an insert command
      var op = new command(Query.getRequestId(), ismaster, bson, ns, [doc], options);
      // Write concern
      var writeConcern = options.writeConcern || {w:1};
      // Create a getLastError command
      var getLastErrorOp = new Query(bson, "system.$cmd", copy(writeConcern, {getLastError: 1}), {numberToReturn: -1});

      // Error out if no connection available
      if(connection == null)
        return _callback(new MongoError("no connection available"));

      try {
        // Execute the insert
        connection.write(op);
        // Write the lastError message
        connection.write(getLastErrorOp);
        // Register the callback
        callbacks.once(getLastErrorOp.requestId, function(err, result) {
          // Get the document
          var doc = result.documents[0];
          // Save the getLastError document
          getLastErrors.push(doc);
          // If we have an error terminate
          if(doc.ok == 0 || doc.err || doc.errmsg) return callback(null, aggregateInsertResults(getLastErrors, connection));
          // Execute the next op in the list
          executeOp(list, callback);
        });
      } catch(err) {
        // We have a serialization error, rewrite as a write error to have same behavior as modern
        // write commands
        getLastErrors.push({ ok: 1, errmsg: err, code: 14 });
        // Return due to an error
        return callback(null, aggregateInsertResults(getLastErrors, connection));
      }
    }

    // Execute the operations
    executeOp(_ops, callback);
  }

  var executeUnordered = function(command, ismaster, ns, bson, pool, callbacks, ops, options, callback) {
    // Bind to current domain
    callback = bindToCurrentDomain(callback);
    // Total operations to write
    var totalOps = ops.length;
    // Collect all the getLastErrors
    var getLastErrors = [];

    for(var i = 0; i < ops.length; i++) {
      // Create an insert command
      var op = new command(Query.getRequestId(), ismaster, bson, ns, [ops[i]], options);
      // Write concern
      var writeConcern = options.writeConcern || {w:1};
      // Create a getLastError command
      var getLastErrorOp = new Query(bson, "system.$cmd", copy(writeConcern, {getLastError: 1}), {numberToReturn: -1});

      // Get a pool connection
      var connection = pool.get();

      // Error out if no connection available
      if(connection == null)
        return _callback(new MongoError("no connection available"));

      try {
        // Execute the insert
        connection.write(op);
        // Write the lastError message
        connection.write(getLastErrorOp);

        // Give the result from getLastError the right index
        var callbackOp = function(_index) {
          return function(err, result) {
            // Update the number of operations executed
            totalOps = totalOps - 1;
            // Save the getLastError document
            getLastErrors[_index] = result.documents[0];
            // Check if we are done
            if(totalOps == 0) {
              callback(null, aggregateInsertResults(getLastErrors, connection));
            }
          }
        }
        // Register the callback
        callbacks.once(getLastErrorOp.requestId, callbackOp(i));
      } catch(err) {
        // Update the number of operations executed
        totalOps = totalOps - 1;
        // We have a serialization error, rewrite as a write error to have same behavior as modern
        // write commands
        getLastErrors[i] = { ok: 1, errmsg: err, code: 14 };
        // Check if we are done
        if(totalOps == 0) {
          callback(null, aggregateInsertResults(getLastErrors, connection));
        }
      }
    }
  }

  //
  // Needs to support legacy mass insert as well as ordered/unordered legacy
  // emulation
  //
  this.insert = function(ismaster, ns, bson, pool, callbacks, ops, options, callback) {
    options = options || {};
    // Default is ordered execution
    var ordered = typeof options.ordered == 'boolean' ? options.ordered : true;
    var legacy = typeof options.legacy == 'boolean' ? options.legacy : false;

    // We are unordered
    if(!ordered) {
      return executeUnordered(Insert, ismaster, ns, bson, pool, callbacks, ops, options, callback);
    }

    return executeOrdered(Insert, ismaster, ns, bson, pool, callbacks, ops, options, callback);
  }

  this.update = function(ismaster, ns, bson, pool, callbacks, ops, options, callback) {
    options = options || {};
    // Default is ordered execution
    var ordered = typeof options.ordered == 'boolean' ? options.ordered : true;

    // We are unordered
    if(!ordered) {
      return executeUnordered(Update, ismaster, ns, bson, pool, callbacks, ops, options, callback);
    }

    return executeOrdered(Update, ismaster, ns, bson, pool, callbacks, ops, options, callback);
  }

  this.remove = function(ismaster, ns, bson, pool, callbacks, ops, options, callback) {
    options = options || {};
    // Default is ordered execution
    var ordered = typeof options.ordered == 'boolean' ? options.ordered : true;

    // We are unordered
    if(!ordered) {
      return executeUnordered(Remove, ismaster, ns, bson, pool, callbacks, ops, options, callback);
    }

    return executeOrdered(Remove, ismaster, ns, bson, pool, callbacks, ops, options, callback);
  }
}

/**
 * @ignore
 */
var bindToCurrentDomain = function(callback) {
  var domain = process.domain;
  if(domain == null || callback == null) {
    return callback;
  } else {
    return domain.bind(callback);
  }
}

module.exports = LegacySupport;<commit_msg>Minor fixes for the legacy support of merging of results<commit_after>var Insert = require('./commands').Insert
  , Update = require('./commands').Update
  , Remove = require('./commands').Remove
  , Query = require('../connection/commands').Query
  , copy = require('../connection/utils').copy
  , CommandResult = require('../topologies/command_result')
  , MongoError = require('../error');

var LegacySupport = function() {

  //
  // Aggregate up all the results
  //
  var aggregateWriteOperationResults = function(results, connection) {
    var final = { ok: 1, n: 0 }

    // Map all the results coming back
    for(var i = 0; i < results.length; i++) {
      var result = results[i];

      // console.dir(result)
      // console.log(result.n)

      // We have a command error
      if(result != null && result.ok == 0 || result.err || result.errmsg) {
        if(result.ok == 0) final.ok = 0;
        final.code = result.code;
        final.errmsg = result.errmsg || result.err || result.errMsg;

        // Check if we have a write error
        if(result.code == 11000
          || result.code == 11001
          || result.code == 12582
          || result.code == 14
          || result.code == 13511) {
          if(final.writeErrors == null) final.writeErrors = [];
          final.writeErrors.push({
              index: i
            , code: result.code
            , errmsg: result.errmsg || result.err || result.errMsg
          });
        } else {
          final.writeConcernError = {
              code: result.code
            , errmsg: result.errmsg || result.err || result.errMsg
          }
        }
      } else if(typeof result.n == 'number') {
        final.n += result.n;
      } else {
        final.n += 1;
      }

      // Result as expected
      if(result != null && result.lastOp) final.lastOp = result.lastOp;
    }

    // Return final aggregated results
    return new CommandResult(final, connection);
  }

  //
  // Execute all inserts in an ordered manner
  //
  var executeOrdered = function(command, ismaster, ns, bson, pool, callbacks, ops, options, callback) {
    var _ops = ops.slice(0);
    // Bind to current domain
    callback = bindToCurrentDomain(callback);
    // Collect all the getLastErrors
    var getLastErrors = [];

    // Execute an operation
    var executeOp = function(list, _callback) {
      // Get a pool connection
      var connection = pool.get();
      // No more items in the list
      if(list.length == 0) return _callback(null, aggregateWriteOperationResults(getLastErrors, connection));

      // Get the first operation
      var doc = list.shift();

      // Create an insert command
      var op = new command(Query.getRequestId(), ismaster, bson, ns, [doc], options);
      // Write concern
      var writeConcern = options.writeConcern || {w:1};
      // Create a getLastError command
      var getLastErrorOp = new Query(bson, "system.$cmd", copy(writeConcern, {getLastError: 1}), {numberToReturn: -1});

      // Error out if no connection available
      if(connection == null)
        return _callback(new MongoError("no connection available"));

      try {
        // Execute the insert
        connection.write(op);
        // Write the lastError message
        connection.write(getLastErrorOp);
        // Register the callback
        callbacks.once(getLastErrorOp.requestId, function(err, result) {
          // Get the document
          var doc = result.documents[0];
          // Save the getLastError document
          getLastErrors.push(doc);
          // If we have an error terminate
          if(doc.ok == 0 || doc.err || doc.errmsg) return callback(null, aggregateWriteOperationResults(getLastErrors, connection));
          // Execute the next op in the list
          executeOp(list, callback);
        });
      } catch(err) {
        // We have a serialization error, rewrite as a write error to have same behavior as modern
        // write commands
        getLastErrors.push({ ok: 1, errmsg: err, code: 14 });
        // Return due to an error
        return callback(null, aggregateWriteOperationResults(getLastErrors, connection));
      }
    }

    // Execute the operations
    executeOp(_ops, callback);
  }

  var executeUnordered = function(command, ismaster, ns, bson, pool, callbacks, ops, options, callback) {
    // Bind to current domain
    callback = bindToCurrentDomain(callback);
    // Total operations to write
    var totalOps = ops.length;
    // Collect all the getLastErrors
    var getLastErrors = [];

    for(var i = 0; i < ops.length; i++) {
      // Create an insert command
      var op = new command(Query.getRequestId(), ismaster, bson, ns, [ops[i]], options);
      // Write concern
      var writeConcern = options.writeConcern || {w:1};
      // Create a getLastError command
      var getLastErrorOp = new Query(bson, "system.$cmd", copy(writeConcern, {getLastError: 1}), {numberToReturn: -1});

      // Get a pool connection
      var connection = pool.get();

      // Error out if no connection available
      if(connection == null)
        return _callback(new MongoError("no connection available"));

      try {
        // Execute the insert
        connection.write(op);
        // Write the lastError message
        connection.write(getLastErrorOp);

        // Give the result from getLastError the right index
        var callbackOp = function(_index) {
          return function(err, result) {
            // Update the number of operations executed
            totalOps = totalOps - 1;
            // Save the getLastError document
            getLastErrors[_index] = result.documents[0];
            // Check if we are done
            if(totalOps == 0) {
              callback(null, aggregateWriteOperationResults(getLastErrors, connection));
            }
          }
        }
        // Register the callback
        callbacks.once(getLastErrorOp.requestId, callbackOp(i));
      } catch(err) {
        // Update the number of operations executed
        totalOps = totalOps - 1;
        // We have a serialization error, rewrite as a write error to have same behavior as modern
        // write commands
        getLastErrors[i] = { ok: 1, errmsg: err, code: 14 };
        // Check if we are done
        if(totalOps == 0) {
          callback(null, aggregateWriteOperationResults(getLastErrors, connection));
        }
      }
    }
  }

  //
  // Needs to support legacy mass insert as well as ordered/unordered legacy
  // emulation
  //
  this.insert = function(ismaster, ns, bson, pool, callbacks, ops, options, callback) {
    options = options || {};
    // Default is ordered execution
    var ordered = typeof options.ordered == 'boolean' ? options.ordered : true;
    var legacy = typeof options.legacy == 'boolean' ? options.legacy : false;
    ops = Array.isArray(ops) ? ops :[ops];

    // We are unordered
    if(!ordered) {
      return executeUnordered(Insert, ismaster, ns, bson, pool, callbacks, ops, options, callback);
    }

    return executeOrdered(Insert, ismaster, ns, bson, pool, callbacks, ops, options, callback);
  }

  this.update = function(ismaster, ns, bson, pool, callbacks, ops, options, callback) {
    options = options || {};
    // Default is ordered execution
    var ordered = typeof options.ordered == 'boolean' ? options.ordered : true;
    ops = Array.isArray(ops) ? ops :[ops];

    // We are unordered
    if(!ordered) {
      return executeUnordered(Update, ismaster, ns, bson, pool, callbacks, ops, options, callback);
    }

    return executeOrdered(Update, ismaster, ns, bson, pool, callbacks, ops, options, callback);
  }

  this.remove = function(ismaster, ns, bson, pool, callbacks, ops, options, callback) {
    options = options || {};
    // Default is ordered execution
    var ordered = typeof options.ordered == 'boolean' ? options.ordered : true;
    ops = Array.isArray(ops) ? ops :[ops];

    // We are unordered
    if(!ordered) {
      return executeUnordered(Remove, ismaster, ns, bson, pool, callbacks, ops, options, callback);
    }

    return executeOrdered(Remove, ismaster, ns, bson, pool, callbacks, ops, options, callback);
  }
}

/**
 * @ignore
 */
var bindToCurrentDomain = function(callback) {
  var domain = process.domain;
  if(domain == null || callback == null) {
    return callback;
  } else {
    return domain.bind(callback);
  }
}

module.exports = LegacySupport;<|endoftext|>


<commit_before>function Network() {
    function sendGameRequest(numPlayers) {
        var message = {"NumPlayers": numPlayers}
        sendMessage(NEW_GAME_CMD, message)
    }

    function sendClientInfo(playerId) {
        var message = {"Id": playerId}
        sendMessage(CLIENT_INFO_CMD, message);
    }

    CLIENT_INFO_CMD = "clientInfo";
    NEW_GAME_CMD = "newGame";

    host = window.location.host.split(":")[0];
    port = "8888"
    ws_addr = "ws://" + host + ":" + port + "/ws"
    conn = new WebSocket(ws_addr);
    DELIM = ":"

    // This function expects the message as a javascript object
    function sendMessage(command, messageObj) {
        var jsonified = JSON.stringify(messageObj)
        packet = command + DELIM + jsonified;
        conn.send(packet)
    }

    // When the connection is open, send some data to the server
    conn.onopen = function () {
        network.sendClientInfo(getPlayerId());
    };

    // Log errors
    conn.onerror = function (error) {
        console.log('WebSocket Error ' + error);
    };

    // Log messages from the server
    conn.onmessage = function (e) {
        console.log('Server: ' + e.data);
    };

    this.sendGameRequest = function(numPlayers) {
        sendGameRequest(numPlayers);
    }

    this.sendClientInfo = function(playerId) {
        sendClientInfo(playerId);
    }
}
<commit_msg>Send world view request and parse the response<commit_after>function Network() {
    // To server functions and constants
    function sendGameRequest(numPlayers) {
        var message = {"NumPlayers": numPlayers}
        sendMessage(NEW_GAME_CMD, message)
    }

    function sendClientInfo(playerId) {
        var message = {"Id": playerId}
        sendMessage(CLIENT_INFO_CMD, message);
    }

    function sendViewWorld() {
        sendMessage(VIEW_WORLD_CMD, {});
    }

    CLIENT_INFO_CMD = "clientInfo";
    NEW_GAME_CMD = "newGame";
    VIEW_WORLD_CMD = "view";

    host = window.location.host.split(":")[0];
    port = "8888"
    ws_addr = "ws://" + host + ":" + port + "/ws"
    conn = new WebSocket(ws_addr);
    DELIM = ":"

    // This function expects the message as a javascript object
    function sendMessage(command, messageObj) {
        var jsonified = JSON.stringify(messageObj)
        packet = command + DELIM + jsonified;
        conn.send(packet)
    }

    // From server functions and constants
    this.packetHandlers = {
        "view": parseViewWorld,
    }

    function parseViewWorld(status, world) {
        console.log(world);
    }

    // When the connection is open, send some data to the server
    conn.onopen = function () {
        network.sendClientInfo(getPlayerId());
    };

    // Log errors
    conn.onerror = function (error) {
        console.log('WebSocket Error ' + error);
    };

    // Log messages from the server
    conn.onmessage = function (e) {
        var cmds = e.data.actualSplit(DELIM, 3);
        console.log(cmds);
        var pkt_type = cmds[0];
        var status = cmds[1];
        if (cmds.length > 2) {
            console.log(cmds[2]);
            var dataObj = JSON.parse(cmds[2]);
            console.log(pkt_type, status, dataObj);
            var handler = this.packetHandlers[pkt_type];
            if (handler != null) {
                handler(status, dataObj);
            } else {
                console.log("Recieved unknown command:", pkt_type);
            }
        }
    };

    this.sendGameRequest = function(numPlayers) {
        sendGameRequest(numPlayers);
    }

    this.sendClientInfo = function(playerId) {
        sendClientInfo(playerId);
    }

    this.sendViewWorld = function() {
        sendViewWorld();
    }
}

String.prototype.actualSplit = function(sep, maxsplit) {
    var split = this.split(sep);
    return maxsplit ? split.slice(0, maxsplit - 1).concat(split.slice(maxsplit - 1, split.length).join(":")) : split;
}
<|endoftext|>


<commit_before>;(function(win,doc,head,body){

  if(!('addEventListener' in win)) return;
  loadStylesheets(['https://fonts.googleapis.com/css?family=Rubik:400,700|Crimson+Text:600,700','https://amdouglas.com/assets/css/article.css']);

  win.addEventListener('load',handleLoad,false);

  function loadStylesheets(urls){
    for(var i = 0;i<urls.length;++i){
      var css  = doc.createElement('link');
      css.rel  = 'stylesheet';
      css.href = urls[i];
      head.appendChild(css);
    }
  }

  function handleLoad(){
    win.removeEventListener('load',handleLoad,false);
    var a=doc.querySelectorAll('input,textarea');
    if('devicePixelRatio' in win && win.devicePixelRatio > 1 && wdth < 992){
      appendOptionalMetaTags();
      appendTouchIcons();
    }
    if('serviceWorker' in navigator){
      navigator.serviceWorker.register('https://amdouglas.com/sw.js', {
        scope: '/'
      }).then(function(registration){
        console.info("SW registered [" + registration.scope + "]");
      }).catch(function(err){
        console.warn("SW failed to register [" + err + "]");
      });
    }
    win.GoogleAnalyticsObject = win.ga;
    win.ga = win.ga || function(){
      for(var p = 0; p < arguments.length; ++p){
        (win.ga.q = win.ga.q || []).push(arguments[p]);
      }
    };
    win.ga.l = 1 * new Date();
    var uas = doc.createElement('script');
    uas.async=!0;
    uas.src='https://www.google-analytics.com/analytics.js';
    body.appendChild(uas);
    win.ga('create','UA-70873652-1','auto');
    win.ga('send','pageview');
  }

  function appendTouchIcons(){
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://amdouglas.com/manifest.json', true);
    xhr.onreadystatechange = function(){
      if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status < 300){
          var data = JSON.parse(xhr.responseText);
          for(var j=0;j<data.icons.length;++j){
            var icon = doc.createElement('link');
            icon.rel = 'apple-touch-icon-precomposed';
            icon.href = data.icons[j].src;
            icon.setAttribute('sizes', data.icons[j].sizes);
            head.appendChild(icon);
          }
        }
      }
    };
    xhr.send();
  }

  function appendOptionalMetaTags(){
    var metatags = ['format-detection','apple-mobile-web-app-status-bar-style','apple-touch-fullscreen','apple-mobile-web-app-capable','mobile-web-app-capable','application-name','MobileOptimized','HandheldFriendly'];
    var metacontents = ['telephone=no,email=no','black-translucent','yes','yes','yes','A. M. Douglas','width','true'];
    var mtl = 8;
    while(mtl--){
      var metatag = doc.createElement('meta');
      metatag.name = metatags[mtl];
      metatag.content = metacontents[mtl];
      head.appendChild(metatag);
    }
  }

})(window,window.document,window.document.head,window.document.body);
<commit_msg>Update article.js<commit_after>;(function(win,doc,head,body){

  if(!('addEventListener' in win)) return;
  loadStylesheets(['https://amdouglas.com/assets/css/fonts.css','https://amdouglas.com/assets/css/article.css']);

  win.addEventListener('load',handleLoad,false);

  function loadStylesheets(urls){
    for(var i = 0;i<urls.length;++i){
      var css  = doc.createElement('link');
      css.rel  = 'stylesheet';
      css.href = urls[i];
      head.appendChild(css);
    }
  }

  function handleLoad(){
    win.removeEventListener('load',handleLoad,false);
    var a=doc.querySelectorAll('input,textarea');
    if('devicePixelRatio' in win && win.devicePixelRatio > 1 && wdth < 992){
      appendOptionalMetaTags();
      appendTouchIcons();
    }
    if('serviceWorker' in navigator){
      navigator.serviceWorker.register('https://amdouglas.com/sw.js', {
        scope: '/'
      }).then(function(registration){
        console.info("SW registered [" + registration.scope + "]");
      }).catch(function(err){
        console.warn("SW failed to register [" + err + "]");
      });
    }
    win.GoogleAnalyticsObject = win.ga;
    win.ga = win.ga || function(){
      for(var p = 0; p < arguments.length; ++p){
        (win.ga.q = win.ga.q || []).push(arguments[p]);
      }
    };
    win.ga.l = 1 * new Date();
    var uas = doc.createElement('script');
    uas.async=!0;
    uas.src='https://www.google-analytics.com/analytics.js';
    body.appendChild(uas);
    win.ga('create','UA-70873652-1','auto');
    win.ga('send','pageview');
  }

  function appendTouchIcons(){
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://amdouglas.com/manifest.json', true);
    xhr.onreadystatechange = function(){
      if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status < 300){
          var data = JSON.parse(xhr.responseText);
          for(var j=0;j<data.icons.length;++j){
            var icon = doc.createElement('link');
            icon.rel = 'apple-touch-icon-precomposed';
            icon.href = data.icons[j].src;
            icon.setAttribute('sizes', data.icons[j].sizes);
            head.appendChild(icon);
          }
        }
      }
    };
    xhr.send();
  }

  function appendOptionalMetaTags(){
    var metatags = ['format-detection','apple-mobile-web-app-status-bar-style','apple-touch-fullscreen','apple-mobile-web-app-capable','mobile-web-app-capable','application-name','MobileOptimized','HandheldFriendly'];
    var metacontents = ['telephone=no,email=no','black-translucent','yes','yes','yes','A. M. Douglas','width','true'];
    var mtl = 8;
    while(mtl--){
      var metatag = doc.createElement('meta');
      metatag.name = metatags[mtl];
      metatag.content = metacontents[mtl];
      head.appendChild(metatag);
    }
  }

})(window,window.document,window.document.head,window.document.body);
<|endoftext|>


<commit_before>/**
 * Comment plugin
 */
(function ($) {

    $.fn.comment = function (method) {
        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof method === 'object' || !method) {
            return methods.init.apply(this, arguments);
        } else {
            $.error('Method ' + method + ' does not exist on jQuery.comment');
            return false;
        }
    };

    // Default settings
    var defaults = {
        // Comment actions buttons selector
        toolsSelector: '.comment-action-buttons',
        // Form selector
        formSelector: '#comment-form',
        // Form container selector
        formContainerSelector: '.comment-form-container',
        // Comment content selector
        contentSelector: '.comment-body',
        // Cancel reply button selector
        cancelReplyBtnSelector: '#cancel-reply',
        //Pjax container id
        pjaxContainerId: '#comment-pjax-container',
        //Pjax default settings
        pjaxSettings: {
            timeout: 10000,
            scrollTo: false,
            url: window.location.href
        }
    };

    // Methods
    var methods = {
        init: function (options) {
            return this.each(function () {
                var $commentForm = $(this);
                if ($commentForm.data('comment')) {
                    return;
                }
                var settings = $.extend({}, defaults, options || {});
                $commentForm.data('comment', settings);
                //Add events
                $commentForm.on('beforeSubmit.comment', beforeSubmitForm);
                var eventParams = {commentForm: $commentForm};
                $(settings.pjaxContainerId).on('click.comment', '[data-action="reply"]', eventParams, reply);
                $(settings.pjaxContainerId).on('click.comment', '[data-action="cancel-reply"]', eventParams, cancelReply);
                $(settings.pjaxContainerId).on('click.comment', '[data-action="delete"]', eventParams, deleteComment);
            });
        },
        data: function () {
            return this.data('comment');
        },
        reset: function (settings) {
            $(settings.pjaxContainerId).each(function () {
                $(this).unbind('.comment');
                $(this).removeData('comment');
            });
            $(settings.formSelector).comment(settings);
        }
    };

    /**
     * This function used for `beforeSubmit` comment form event
     */
    function beforeSubmitForm() {
        var $commentForm = $(this),
            settings = $commentForm.data('comment');
        //Add loading to comment button
        $commentForm.find(':submit').prop('disabled', true).text('Loading...');
        var pjaxSettings = $.extend({container: settings.pjaxContainerId}, settings.pjaxSettings);
        //Send post request
        $.post($commentForm.attr("action"), $commentForm.serialize(), function (data) {
            //If success is status, then pjax container has been reloaded and comment form has been reset
            if (data.status == 'success') {
                $.pjax(pjaxSettings).done(function () {
                    $commentForm.find(':submit').prop('disabled', false).text('Comment');
                    $commentForm.trigger("reset");
                    //Restart plugin
                    methods.reset.call($commentForm, settings);
                });
            }
            //If status is error, then only show form errors.
            else {
                if (data.hasOwnProperty('errors')) {
                    $commentForm.yiiActiveForm('updateMessages', data.errors, true);
                }
                else {
                    $commentForm.yiiActiveForm('updateAttribute', 'commentmodel-content', [data.message]);
                }
                $commentForm.find(':submit').prop('disabled', false).text('Comment');
            }
        });
        return false;
    }

    /**
     * Reply to comment
     * @param event
     */
    function reply(event) {
        event.preventDefault();
        var $commentForm = event.data.commentForm;
        var settings = $commentForm.data('comment');
        var $this = $(this);
        var parentCommentSelector = $this.parents('[data-comment-content-id="' + $this.data('comment-id') + '"]');
        //Move form to comment container
        $commentForm.appendTo(parentCommentSelector);
        //Update parentId field
        $commentForm.find('[data-comment="parent-id"]').val($this.data('comment-id'));
        //Show cancel reply link
        $commentForm.find(settings.cancelReplyBtnSelector).show();
    }

    /**
     * Cancel reply
     * @param event
     */
    function cancelReply(event) {
        event.preventDefault();
        var $commentForm = event.data.commentForm;
        var settings = $commentForm.data('comment');
        $commentForm.find(settings.cancelReplyBtnSelector).hide();
        //Move form back to form container
        var formContainer = $(settings.pjaxContainerId).find(settings.formContainerSelector);
        $commentForm.prependTo(formContainer);
        //Update parentId field
        $commentForm.find('[data-comment="parent-id"]').val(null);
    }

    /**
     * Delete comment
     * @param event
     */
    function deleteComment(event) {
        event.preventDefault();
        var $commentForm = event.data.commentForm;
        var settings = $commentForm.data('comment');
        var $this = $(this);
        $.ajax({
            url: $this.data('url'),
            type: 'DELETE',
            error: function (xhr, status, error) {
                alert(error);
            },
            success: function (result, status, xhr) {
                $this.parents('[data-comment-content-id="' + $this.data('comment-id') + '"]').find(settings.contentSelector).text(result);
                $this.parents(settings.toolsSelector).remove();
            }
        });
    }

})(window.jQuery);
<commit_msg>add submitBtnText and submitBtnLoadingText properties to comment.js<commit_after>/**
 * Comment plugin
 */
(function ($) {

    $.fn.comment = function (method) {
        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof method === 'object' || !method) {
            return methods.init.apply(this, arguments);
        } else {
            $.error('Method ' + method + ' does not exist on jQuery.comment');
            return false;
        }
    };

    // Default settings
    var defaults = {
        toolsSelector: '.comment-action-buttons',
        formSelector: '#comment-form',
        formContainerSelector: '.comment-form-container',
        contentSelector: '.comment-body',
        cancelReplyBtnSelector: '#cancel-reply',
        pjaxContainerId: '#comment-pjax-container',
        pjaxSettings: {
            timeout: 10000,
            scrollTo: false,
            url: window.location.href
        },
        submitBtnText: 'Comment',
        submitBtnLoadingText: 'Loading...'
    };

    // Methods
    var methods = {
        init: function (options) {
            return this.each(function () {
                var $commentForm = $(this);
                if ($commentForm.data('comment')) {
                    return;
                }
                var settings = $.extend({}, defaults, options || {});
                $commentForm.data('comment', settings);
                $commentForm.on('beforeSubmit.comment', beforeSubmitForm);
                var eventParams = {commentForm: $commentForm};
                $(settings.pjaxContainerId).on('click.comment', '[data-action="reply"]', eventParams, reply);
                $(settings.pjaxContainerId).on('click.comment', '[data-action="cancel-reply"]', eventParams, cancelReply);
                $(settings.pjaxContainerId).on('click.comment', '[data-action="delete"]', eventParams, deleteComment);
            });
        },
        data: function () {
            return this.data('comment');
        },
        reset: function (settings) {
            $(settings.pjaxContainerId).each(function () {
                $(this).unbind('.comment');
                $(this).removeData('comment');
            });
            $(settings.formSelector).comment(settings);
        }
    };

    /**
     * This function used for `beforeSubmit` comment form event
     */
    function beforeSubmitForm() {
        var $commentForm = $(this);
        var settings = $commentForm.data('comment');
        var pjaxSettings = $.extend({container: settings.pjaxContainerId}, settings.pjaxSettings);

        $commentForm.find(':submit').prop('disabled', true).text(settings.submitBtnLoadingText);

        $.post($commentForm.attr("action"), $commentForm.serialize(), function (data) {
            if (data.status == 'success') {
                $.pjax(pjaxSettings).done(function () {
                    $commentForm.trigger("reset");
                    methods.reset.call($commentForm, settings);
                });
            }
            else {
                if (data.hasOwnProperty('errors')) {
                    $commentForm.yiiActiveForm('updateMessages', data.errors, true);
                }
                else {
                    $commentForm.yiiActiveForm('updateAttribute', 'commentmodel-content', [data.message]);
                }
                $commentForm.find(':submit').prop('disabled', false).text(settings.submitBtnText);
            }
        });

        return false;
    }

    /**
     * Reply to comment
     * @param event
     */
    function reply(event) {
        event.preventDefault();
        var $commentForm = event.data.commentForm;
        var settings = $commentForm.data('comment');
        var $this = $(this);
        var parentCommentSelector = $this.parents('[data-comment-content-id="' + $this.data('comment-id') + '"]');
        $commentForm.appendTo(parentCommentSelector);
        $commentForm.find('[data-comment="parent-id"]').val($this.data('comment-id'));
        $commentForm.find(settings.cancelReplyBtnSelector).show();
    }

    /**
     * Cancel reply
     * @param event
     */
    function cancelReply(event) {
        event.preventDefault();
        var $commentForm = event.data.commentForm;
        var settings = $commentForm.data('comment');
        $commentForm.find(settings.cancelReplyBtnSelector).hide();
        var formContainer = $(settings.pjaxContainerId).find(settings.formContainerSelector);
        $commentForm.prependTo(formContainer);
        $commentForm.find('[data-comment="parent-id"]').val(null);
    }

    /**
     * Delete the comment
     * @param event
     */
    function deleteComment(event) {
        event.preventDefault();
        var $commentForm = event.data.commentForm;
        var settings = $commentForm.data('comment');
        var $this = $(this);
        $.ajax({
            url: $this.data('url'),
            type: 'DELETE',
            error: function (xhr, status, error) {
                alert(error);
            },
            success: function (result, status, xhr) {
                $this.parents('[data-comment-content-id="' + $this.data('comment-id') + '"]').find(settings.contentSelector).text(result);
                $this.parents(settings.toolsSelector).remove();
            }
        });
    }

})(window.jQuery);<|endoftext|>


<commit_before>/**
 *
 */
export default class CharacterSpan {

    /**
     * Create a new {CharacterSpan} including all characters between startCharCode and endCharCode.
     * @param startCharCode Char code of the first character included in the span.
     * @param endCharCode Char code of the last character included in the span. Must be greater than or equals to startCharCode.
     */
    constructor(startCharCode, endCharCode) {
        this._startCharCode = startCharCode;
        this._endCharCode = endCharCode;
    }

    /**
     * Create a new {CharacterSpan} including all characters between startCharacter and endCharacter.
     * @param {String} startCharacter First character included in the span.
     * @param {String} endCharacter Char code of the last character included in the span. Must be greater than or equals to startCharCode.
     * @return {CharacterSpan} a new span.
     */
    static newRange(startCharacter, endCharacter) {

    }

    /**
     * Create a new {CharacterSpan} including only the provided character.
     * @param charCode Character included in the span.
     * @return {CharacterSpan} a new span.
     */
    static newSingleCharacter(character) {

    }

    /**
     * Check if the span contains the provided character
     * @param {String|Number} character Char code or character to test.
     * @return {Boolean} true if span includes the character, false otherwise.
     */
    contains(character) {
        let charCode;
        if (character instanceof String) {
            charCode = character.charCodeAt(0);
        } else {
            charCode = character;
        }
        return charCode >= this._startCharCode && charCode <= this._endCharCode;
    }
}
<commit_msg>Fixed factory method<commit_after>/**
 *
 */
export default class CharacterSpan {

    /**
     * Create a new {CharacterSpan} including all characters between startCharCode and endCharCode.
     * @param startCharCode Char code of the first character included in the span.
     * @param endCharCode Char code of the last character included in the span. Must be greater than or equals to startCharCode.
     */
    constructor(startCharCode, endCharCode) {
        this._startCharCode = startCharCode;
        this._endCharCode = endCharCode;
    }

    /**
     * Create a new {CharacterSpan} including all characters between startCharacter and endCharacter.
     * @param {String} startCharacter First character included in the span.
     * @param {String} endCharacter Char code of the last character included in the span. Must be greater than or equals to startCharCode.
     * @return {CharacterSpan} a new span.
     */
    static newRange(startCharacter, endCharacter) {
        return new CharacterSpan(startCharacter, endCharacter);
    }

    /**
     * Create a new {CharacterSpan} including only the provided character.
     * @param charCode Character included in the span.
     * @return {CharacterSpan} a new span.
     */
    static newSingleCharacter(character) {
        return new CharacterSpan(character, character);
    }

    /**
     * Check if the span contains the provided character
     * @param {String|Number} character Char code or character to test.
     * @return {Boolean} true if span includes the character, false otherwise.
     */
    contains(character) {
        let charCode;
        if (character instanceof String) {
            charCode = character.charCodeAt(0);
        } else {
            charCode = character;
        }
        return charCode >= this._startCharCode && charCode <= this._endCharCode;
    }
}
<|endoftext|>


<commit_before>'use strict';

var _         = require('lodash');
var d3        = require('d3');
var moment    = require('moment');

var dateUtil  = require('util/date');

var lineChart = require('./renderer/line');

module.exports = {

	mixins: [
		require('./line')
	],

	ready: function () {
		console.info('year-over-year::ready');
		console.debug(this);
	},

	computed: {

		campaign_start: function () {
			if (!this.period) {
				return null;
			}

			var start = moment(this.campaign.date, 'YYYYMMDD');

			start = start.subtract.apply(
					start,
					dateUtil.parseDuration(this.period)
				)
				.startOf('year')
				.format('YYYY-MM-DD');

			console.debug('year-over-year::campaign_start', start);
			return start;
		},

		renderer: function () {
			var x     = this.xScale;
			var y     = this.yScale;
			var color = this.colorScale;

			var renderer = lineChart()
				.x(function (d) {
					return x(d.campaign.start_date.getMonth());
				})
				.y(function (d) {
					return y(d.value);
				})
				.color(function (d, i) {
					return color(i);
				});

			return renderer;
		},

		series: function () {

			if (this.empty) {
				return [];
			}

			var series = _(this.datapoints)
				.groupBy(function (d) {
					return d.campaign.start_date.getFullYear();
				})
				.values()
				.map(function (d) {
					return _.sortBy(d, function (o) {
						return o.campaign.start_date;
					});
				})
				.value();

			console.debug('year-over-year::series', series);
			return series;
		},

		xFmt: function () {
			return function (d) {
				return moment().month(d).format('MMM');
			};
		},

		xScale: function () {
			var scale = d3.scale.linear()
				.domain([0, 11])
				.range([0, this.contentWidth]);

			return scale;
		},

		xTicks: function () {
			if (!this.campaign) {
				return [0, 5, 11];
			}

			var ticks = [];
			var now = moment(this.campaign.value, 'YYYY-MM-DD').month();
			console.debug('year-over-year::xTicks now', now);

			// If the current month is April or later, show January
			if (now > 2) {
				ticks.push(0);
			}

			// If the current month is after August, show June
			if (now > 7) {
				ticks.push(5);
			}

			ticks.push(now);

			// if the current month is before November, show December
			if (now < 9) {
				ticks.push(11);
			}

			return ticks;
		}

	}

};
<commit_msg>Remove some debuggative print statements<commit_after>'use strict';

var _         = require('lodash');
var d3        = require('d3');
var moment    = require('moment');

var dateUtil  = require('util/date');

var lineChart = require('./renderer/line');

module.exports = {

	mixins: [
		require('./line')
	],

	computed: {

		campaign_start: function () {
			if (!this.period) {
				return null;
			}

			var start = moment(this.campaign.date, 'YYYYMMDD');

			start = start.subtract.apply(
					start,
					dateUtil.parseDuration(this.period)
				)
				.startOf('year')
				.format('YYYY-MM-DD');

			console.debug('year-over-year::campaign_start', start);
			return start;
		},

		renderer: function () {
			var x     = this.xScale;
			var y     = this.yScale;
			var color = this.colorScale;

			var renderer = lineChart()
				.x(function (d) {
					return x(d.campaign.start_date.getMonth());
				})
				.y(function (d) {
					return y(d.value);
				})
				.color(function (d, i) {
					return color(i);
				});

			return renderer;
		},

		series: function () {

			if (this.empty) {
				return [];
			}

			var series = _(this.datapoints)
				.groupBy(function (d) {
					return d.campaign.start_date.getFullYear();
				})
				.values()
				.map(function (d) {
					return _.sortBy(d, function (o) {
						return o.campaign.start_date;
					});
				})
				.value();

			return series;
		},

		xFmt: function () {
			return function (d) {
				return moment().month(d).format('MMM');
			};
		},

		xScale: function () {
			var scale = d3.scale.linear()
				.domain([0, 11])
				.range([0, this.contentWidth]);

			return scale;
		},

		xTicks: function () {
			if (!this.campaign) {
				return [0, 5, 11];
			}

			var ticks = [];
			var now = moment(this.campaign.value, 'YYYY-MM-DD').month();
			console.debug('year-over-year::xTicks now', now);

			// If the current month is April or later, show January
			if (now > 2) {
				ticks.push(0);
			}

			// If the current month is after August, show June
			if (now > 7) {
				ticks.push(5);
			}

			ticks.push(now);

			// if the current month is before November, show December
			if (now < 9) {
				ticks.push(11);
			}

			return ticks;
		}

	}

};
<|endoftext|>


<commit_before>function newId() {
  return Math.random().toString(36).substring(7);
}

var user_name = "";

$(document).ready( function() {

  function saveCard( id, text, x, y, user ) {
    socket.emit('saveCard',
      { "id": id, "text": text, "x": x, "y": y, "user": user } );
  }

  function dropped( event, ui ) {
    var x = parseInt( ui.offset.left );
    var y = parseInt( ui.offset.top );
    var id = ui.helper.attr("id");
    var text = ui.helper.find(".text").text();
    ui.helper.find(".user").text( user_name );
    saveCard( id, text, x, y, user_name );
  }

  function cardContent( text, user ) {
    return "" + text + "
" +
      "" + user + ""
  }

  function newCard( id, x, y, text, user ) {
    var element = "
 " +
      cardContent( text, user ) +
      "
";
    $(element).appendTo(".canvas")
      .css("position","absolute")
      .css("left",x + "px")
      .css("top",y + "px")
      .draggable( { stop: dropped } );
  }

  var socket = io.connect('/');

  socket.on('cards', function( data ) {
    data.forEach( function( card ) {
      newCard( card.id, card.x, card.y, card.text, card.user );
    });
    $(".loading").fadeOut("fast");
  });

  socket.on('card', function(data) {
    var card = $(".card#" + data["id"]);
    if (card.length) {
      card.css("left",data["x"] + "px");
      card.css("top",data["y"] + "px");
    } else {
      newCard( data["id"], data["x"], data["y"], data["text"], data["user"] );
    }
  });

  socket.on('trash', function(data) {
    $(".card#" + data["id"]).remove();
  });

  socket.on('clear', function(data) {
    $('.card').remove();
  });

  $('.trashcan').droppable( {
    drop: function(event,ui) {
      var id = ui.helper.attr("id");
      socket.emit("trash", { "id": id });
      ui.helper.remove();
    }
  });

  $('.canvas').click( function(e) {
    var x = e.pageX;
    var y = e.pageY;

    var id = newId();

    var card = "
" +
        "
" +
          "
Enter text for card:
" +
          "<input class='card-input-field' type='text'></input>" +
        "
" +
      "
";

    $(".card.new-card").remove();

    $(card).appendTo(".canvas")
      .addClass("new-card")
      .css("position","absolute")
      .css("left",x)
      .css("top",y)
      .draggable( { stop: dropped } )
      .keyup(function(ev) {
        if ( ev.which === 13 ) {
          var text = $('.card-input-field').val();
          $('.card-input').replaceWith( cardContent( text, user_name ) );
          $('.card').removeClass("new-card");
          saveCard( id, text, x, y, user_name );
        }
      })

    $('.card-input-field').focus();
  });

  $('.menu .clear').click( function(e) {
    $('.card').remove();
    socket.emit("clear");
  });

  function saveName() {
    user_name = $("#name-entry").val();
    $("Welcome, " + user_name + "!").appendTo('.welcome');
    $('#name-dialog').dialog("close");
  }

  $('#name-entry').keyup(function(ev) {
    if ( ev.which === 13 ) {
      saveName();
    }
  });

  $('#name-dialog').dialog({
    modal: true,
    buttons: { "Continue": saveName }
  });
});
<commit_msg>Remember name locally.<commit_after>function newId() {
  return Math.random().toString(36).substring(7);
}

var user_name = localStorage.getItem("userName");

$(document).ready( function() {

  function saveCard( id, text, x, y, user ) {
    socket.emit('saveCard',
      { "id": id, "text": text, "x": x, "y": y, "user": user } );
  }

  function dropped( event, ui ) {
    var x = parseInt( ui.offset.left );
    var y = parseInt( ui.offset.top );
    var id = ui.helper.attr("id");
    var text = ui.helper.find(".text").text();
    ui.helper.find(".user").text( user_name );
    saveCard( id, text, x, y, user_name );
  }

  function cardContent( text, user ) {
    return "" + text + "
" +
      "" + user + ""
  }

  function newCard( id, x, y, text, user ) {
    var element = "
 " +
      cardContent( text, user ) +
      "
";
    $(element).appendTo(".canvas")
      .css("position","absolute")
      .css("left",x + "px")
      .css("top",y + "px")
      .draggable( { stop: dropped } );
  }

  var socket = io.connect('/');

  socket.on('cards', function( data ) {
    data.forEach( function( card ) {
      newCard( card.id, card.x, card.y, card.text, card.user );
    });
    $(".loading").fadeOut("fast");
  });

  socket.on('card', function(data) {
    var card = $(".card#" + data["id"]);
    if (card.length) {
      card.css("left",data["x"] + "px");
      card.css("top",data["y"] + "px");
    } else {
      newCard( data["id"], data["x"], data["y"], data["text"], data["user"] );
    }
  });

  socket.on('trash', function(data) {
    $(".card#" + data["id"]).remove();
  });

  socket.on('clear', function(data) {
    $('.card').remove();
  });

  $('.trashcan').droppable( {
    drop: function(event,ui) {
      var id = ui.helper.attr("id");
      socket.emit("trash", { "id": id });
      ui.helper.remove();
    }
  });

  $('.canvas').click( function(e) {
    var x = e.pageX;
    var y = e.pageY;

    var id = newId();

    var card = "
" +
        "
" +
          "
Enter text for card:
" +
          "<input class='card-input-field' type='text'></input>" +
        "
" +
      "
";

    $(".card.new-card").remove();

    $(card).appendTo(".canvas")
      .addClass("new-card")
      .css("position","absolute")
      .css("left",x)
      .css("top",y)
      .draggable( { stop: dropped } )
      .keyup(function(ev) {
        if ( ev.which === 13 ) {
          var text = $('.card-input-field').val();
          $('.card-input').replaceWith( cardContent( text, user_name ) );
          $('.card').removeClass("new-card");
          saveCard( id, text, x, y, user_name );
        }
      })

    $('.card-input-field').focus();
  });

  $('.menu .clear').click( function(e) {
    $('.card').remove();
    socket.emit("clear");
  });

  function saveName() {
    user_name = $("#name-entry").val();
    localStorage.setItem("userName", user_name );
    $("Welcome, " + user_name + "!").appendTo('.welcome');
    $('#name-dialog').dialog("close");
  }

  $('#name-entry').keyup(function(ev) {
    if ( ev.which === 13 ) {
      saveName();
    }
  });

  $('#name-dialog').dialog({
    modal: true,
    buttons: { "Continue": saveName },
    create: function(event, ui) {
      $('#name-entry').val( user_name );
    }
  });
});
<|endoftext|>


<commit_before>
    // handles interfacing with GIFEncoder
    var MFAnimatedGIF = function(opts) {
        var encoder;

        var _initialize = function(opts) {

            var canvas = document.createElement("canvas");
            var context = canvas.getContext('2d');

            encoder = new GIFEncoder();
            encoder.setRepeat(opts.repeat);
            encoder.setDelay(opts.delay);
            canvas.width =  opts.width;
            canvas.height = opts.height;
            encoder.setSize(opts.width, opts.height);

            encoder.start();

            for(var i=0; i<opts.images.length; i++) {
                var animframe = opts.images[i];
                var ctx = canvas.getContext('2d');

                ctx.drawImage(animframe, 0, 0, animframe.width, animframe.height, 0, 0, canvas.width, canvas.height);

                encoder.addFrame(ctx);
            }

            encoder.finish();
        };

        var _dataURL = function() {
            return 'data:image/gif;base64,' + $.base64.encode(encoder.stream().getData());
        };

        var _binaryURL = function() {
            // Convert encoder data to binary format
            var data = encoder.stream().getData();
            var byteArray = new Uint8Array(data.length);
            for (var i = 0; i < data.length; i++) {
                byteArray[i] = data.charCodeAt(i) & 0xff;
            }

            var bb = new BlobBuilder();
            bb.append(byteArray.buffer);
            return window.URL.createObjectURL(bb.getBlob("image/gif"));
        };

        _initialize(opts);

        return {
            dataURL: _dataURL,
            binaryURL: _binaryURL
        };
    };

    var App = {};
    App.MAX_BYTES = 2*1024*1024; // 2MB
    App.initialRate = 300; // 300ms delay in frames
    App.timeline = [];

    var fileList = $("#inimglist");
    var body = $("body");

    // Bail out if the browser doesn't support required features
    // blobbuilder and a[download] are not required, as there is a fallback
    if (!FileReaderJS.enabled || !Modernizr.draganddrop) {
        body.addClass("disabled");
    }

    // drag and drop setup.
    var opts = {
        accept: 'image/*',
        on: {
            beforestart: function(file) {
                if (file.size > App.MAX_BYTES) {
                    return false;
                }
            },
            error: function(file) {
                fileList.append("
Error
");
            },
            skip: function(file) {
                fileList.append("
Skip
");
            },
            load: function(e, file) {
                fileList.append("

");

                var originalimg = new Image();

                if (App.timeline.length === 0){
                    // TODO: do we need this?
                    // App.timeline[0].width seems to work fine when called in groupend
                    originalimg.onload = function(){
                        App.initialWidth    = originalimg.width;
                        App.initialHeight   = originalimg.height;
                        $(window).trigger('firstimage');
                    };
                }

                originalimg.src = e.target.result;
                App.timeline.push(originalimg);
            },
            groupstart: function(group) {
                body.addClass("hasfiles");
            },
            groupend: function(group) {
                // TOOOOOO SLOW
                //buildGif();
            }
        }
    };
    // the library handles most of the dnd bits.
    FileReaderJS.setupDrop(document.body, opts);
    FileReaderJS.setupClipboard(document.body, opts);

    // test to see if we can do cool download or fallback style.
        Modernizr.addTest({
            // BlobBuilder
            "blobbuilder": function() {
                return  !!(window.BlobBuilder || window.MozBlobBuilder || window.WebKitBlobBuilder);
            },
            // Blob URL support
            "bloburls": function() {
                return !!(window.URL && window.URL.createObjectURL || window.webkitURL && window.webkitURL.createObjectURL);
            },
            // download attribute
            "download": function() {
                return "download" in document.createElement("a");
            },
            // FormData
            "formdata": function() {
                return  !!(window.FormData && "append" in window.FormData.prototype);
            }
        });

        if(!Modernizr.download && $('#saveabro')) {
            var iframe = document.createElement("iframe");

            iframe.src = "http://saveasbro.com/";
            iframe.setAttribute("style","position: absolute; visibility: hidden; left: -999em;");
            iframe.id = "saveasbro";
            document.body.appendChild(iframe);
        }

    var finalImage = $("#animresult");
    $(".clear").on('click', function() {
        finalImage.attr("src", "about:blank").hide();
        fileList.empty();
        App.timeline = [];
        return false;
    });

    // kick off GIF generation and download prep
    $('.play').on('click', function(e) {
        buildGif();
        return false;
    }); // eo $('.play').on('click', function(e) {

    function buildGif() {

        if (!App.timeline.length) {
            return;
        }

        var mfAnimatedGIF = new MFAnimatedGIF({
            images: App.timeline,
            delay : App.rate,
            repeat: false,

            // use dimensions from first image as default
            height: App.initialWidth || App.timeline[0].height,
            width : App.initialWidth || App.timeline[0].width
        });

        $('#animresult').attr('src', mfAnimatedGIF.dataURL());

        //
        // Create the download link
        //
        window.URL = window.webkitURL || window.URL;
        window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;

        var filename = "animated.gif";

        var a = document.querySelector('#downloadlink');
        a.style.display = "none";

        if(Modernizr.download && Modernizr.bloburls && Modernizr.blobbuilder) {
            a.download = filename;
            a.href = mfAnimatedGIF.binaryURL(filename);
            a.style.display = "inline-block";

            a.onclick = function(e) {
              // Need a small delay for the revokeObjectURL to work properly.
              setTimeout(function() {
                window.URL.revokeObjectURL(a.href);
              }, 1500);
            };
        } else {
            window.onmessage = function(e) {
                e = e || window.event;

                var origin = e.origin || e.domain || e.uri;
                if(origin !== "http://saveasbro.com") return;
                a.href = "http://saveasbro.com/gif/" + e.data;
                a.style.display = "inline-block";
            };

            var iframe = document.querySelector('#saveasbro');
            iframe.contentWindow.postMessage(JSON.stringify({name:filename, data: mfAnimatedGIF.dataURL(), formdata: Modernizr.formdata}),"http://saveasbro.com");
        }

    }

<commit_msg>update UI for clear<commit_after>
    // handles interfacing with GIFEncoder
    var MFAnimatedGIF = function(opts) {
        var encoder;

        var _initialize = function(opts) {

            var canvas = document.createElement("canvas");
            var context = canvas.getContext('2d');

            encoder = new GIFEncoder();
            encoder.setRepeat(opts.repeat);
            encoder.setDelay(opts.delay);
            canvas.width =  opts.width;
            canvas.height = opts.height;
            encoder.setSize(opts.width, opts.height);

            encoder.start();

            for(var i=0; i<opts.images.length; i++) {
                var animframe = opts.images[i];
                var ctx = canvas.getContext('2d');

                ctx.drawImage(animframe, 0, 0, animframe.width, animframe.height, 0, 0, canvas.width, canvas.height);

                encoder.addFrame(ctx);
            }

            encoder.finish();
        };

        var _dataURL = function() {
            return 'data:image/gif;base64,' + $.base64.encode(encoder.stream().getData());
        };

        var _binaryURL = function() {
            // Convert encoder data to binary format
            var data = encoder.stream().getData();
            var byteArray = new Uint8Array(data.length);
            for (var i = 0; i < data.length; i++) {
                byteArray[i] = data.charCodeAt(i) & 0xff;
            }

            var bb = new BlobBuilder();
            bb.append(byteArray.buffer);
            return window.URL.createObjectURL(bb.getBlob("image/gif"));
        };

        _initialize(opts);

        return {
            dataURL: _dataURL,
            binaryURL: _binaryURL
        };
    };

    var App = {};
    App.MAX_BYTES = 2*1024*1024; // 2MB
    App.initialRate = 300; // 300ms delay in frames
    App.timeline = [];

    App.clear = function() {
        finalImage.attr("src", "about:blank").hide();
        fileList.empty();
        App.timeline = [];
        body.removeClass("hasfiles");
    };

    var fileList = $("#inimglist");
    var body = $("body");

    // Bail out if the browser doesn't support required features
    // blobbuilder and a[download] are not required, as there is a fallback
    if (!FileReaderJS.enabled || !Modernizr.draganddrop) {
        body.addClass("disabled");
    }

    // drag and drop setup.
    var opts = {
        accept: 'image/*',
        on: {
            beforestart: function(file) {
                if (file.size > App.MAX_BYTES) {
                    return false;
                }
            },
            error: function(file) {
                fileList.append("
Error
");
            },
            skip: function(file) {
                fileList.append("
Skip
");
            },
            load: function(e, file) {
                fileList.append("

");

                var originalimg = new Image();

                if (App.timeline.length === 0){
                    // TODO: do we need this?
                    // App.timeline[0].width seems to work fine when called in groupend
                    originalimg.onload = function(){
                        App.initialWidth    = originalimg.width;
                        App.initialHeight   = originalimg.height;
                        $(window).trigger('firstimage');
                    };
                }

                originalimg.src = e.target.result;
                App.timeline.push(originalimg);
            },
            groupstart: function(group) {
                body.addClass("hasfiles");
            },
            groupend: function(group) {
                // TOOOOOO SLOW
                //buildGif();
            }
        }
    };
    // the library handles most of the dnd bits.
    FileReaderJS.setupDrop(document.body, opts);
    FileReaderJS.setupClipboard(document.body, opts);

    // test to see if we can do cool download or fallback style.
        Modernizr.addTest({
            // BlobBuilder
            "blobbuilder": function() {
                return  !!(window.BlobBuilder || window.MozBlobBuilder || window.WebKitBlobBuilder);
            },
            // Blob URL support
            "bloburls": function() {
                return !!(window.URL && window.URL.createObjectURL || window.webkitURL && window.webkitURL.createObjectURL);
            },
            // download attribute
            "download": function() {
                return "download" in document.createElement("a");
            },
            // FormData
            "formdata": function() {
                return  !!(window.FormData && "append" in window.FormData.prototype);
            }
        });

        if(!Modernizr.download && $('#saveabro')) {
            var iframe = document.createElement("iframe");

            iframe.src = "http://saveasbro.com/";
            iframe.setAttribute("style","position: absolute; visibility: hidden; left: -999em;");
            iframe.id = "saveasbro";
            document.body.appendChild(iframe);
        }

    var finalImage = $("#animresult");
    $(".clear").on('click', function() {
        App.clear();
        return false;
    });

    // kick off GIF generation and download prep
    $('.play').on('click', function(e) {
        buildGif();
        return false;
    }); // eo $('.play').on('click', function(e) {

    function buildGif() {

        if (!App.timeline.length) {
            return;
        }

        var mfAnimatedGIF = new MFAnimatedGIF({
            images: App.timeline,
            delay : App.rate,
            repeat: false,

            // use dimensions from first image as default
            height: App.initialWidth || App.timeline[0].height,
            width : App.initialWidth || App.timeline[0].width
        });

        $('#animresult').attr('src', mfAnimatedGIF.dataURL());

        //
        // Create the download link
        //
        window.URL = window.webkitURL || window.URL;
        window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;

        var filename = "animated.gif";

        var a = document.querySelector('#downloadlink');
        a.style.display = "none";

        if(Modernizr.download && Modernizr.bloburls && Modernizr.blobbuilder) {
            a.download = filename;
            a.href = mfAnimatedGIF.binaryURL(filename);
            a.style.display = "inline-block";

            a.onclick = function(e) {
              // Need a small delay for the revokeObjectURL to work properly.
              setTimeout(function() {
                window.URL.revokeObjectURL(a.href);
              }, 1500);
            };
        } else {
            window.onmessage = function(e) {
                e = e || window.event;

                var origin = e.origin || e.domain || e.uri;
                if(origin !== "http://saveasbro.com") return;
                a.href = "http://saveasbro.com/gif/" + e.data;
                a.style.display = "inline-block";
            };

            var iframe = document.querySelector('#saveasbro');
            iframe.contentWindow.postMessage(JSON.stringify({name:filename, data: mfAnimatedGIF.dataURL(), formdata: Modernizr.formdata}),"http://saveasbro.com");
        }

    }

<|endoftext|>


<commit_before>import extend from 'extend';
import Emitter from '../core/emitter';
import BaseTheme, { BaseTooltip } from './base';
import LinkBlot from '../formats/link';
import { Range } from '../core/selection';
import icons from '../ui/icons';

const TOOLBAR_CONFIG = [
  [{ header: ['1', '2', '3', false] }],
  ['bold', 'italic', 'underline', 'link'],
  [{ list: 'ordered' }, { list: 'bullet' }],
  ['clean'],
];

class SnowTooltip extends BaseTooltip {
  constructor(quill, bounds) {
    super(quill, bounds);
    this.preview = this.root.querySelector('a.ql-preview');
  }

  listen() {
    super.listen();
    this.root.querySelector('a.ql-action').addEventListener('click', event => {
      if (this.root.classList.contains('ql-editing')) {
        this.save();
      } else {
        this.edit('link', this.preview.textContent);
      }
      event.preventDefault();
    });
    this.root.querySelector('a.ql-remove').addEventListener('click', event => {
      if (this.linkRange != null) {
        const range = this.linkRange;
        this.restoreFocus();
        this.quill.formatText(range, 'link', false, Emitter.sources.USER);
        delete this.linkRange;
      }
      event.preventDefault();
      this.hide();
    });
    this.quill.on(
      Emitter.events.SELECTION_CHANGE,
      (range, oldRange, source) => {
        if (range == null) return;
        if (range.length === 0 && source === Emitter.sources.USER) {
          const [link, offset] = this.quill.scroll.descendant(
            LinkBlot,
            range.index,
          );
          if (link != null) {
            this.linkRange = new Range(range.index - offset, link.length());
            const preview = LinkBlot.formats(link.domNode);
            this.preview.textContent = preview;
            this.preview.setAttribute('href', preview);
            this.show();
            this.position(this.quill.getBounds(this.linkRange));
            return;
          }
        } else {
          delete this.linkRange;
        }
        this.hide();
      },
    );
  }

  show() {
    super.show();
    this.root.removeAttribute('data-mode');
  }
}
SnowTooltip.TEMPLATE = [
  '',
  '<input type="text" data-formula="e=mc^2" data-link="https://quilljs.com" data-video="Embed URL">',
  '',
  '',
].join('');

class SnowTheme extends BaseTheme {
  constructor(quill, options) {
    if (
      options.modules.toolbar != null &&
      options.modules.toolbar.container == null
    ) {
      options.modules.toolbar.container = TOOLBAR_CONFIG;
    }
    super(quill, options);
    this.quill.container.classList.add('ql-snow');
  }

  extendToolbar(toolbar) {
    toolbar.container.classList.add('ql-snow');
    this.buildButtons(toolbar.container.querySelectorAll('button'), icons);
    this.buildPickers(toolbar.container.querySelectorAll('select'), icons);
    this.tooltip = new SnowTooltip(this.quill, this.options.bounds);
    if (toolbar.container.querySelector('.ql-link')) {
      this.quill.keyboard.addBinding(
        { key: 'k', shortKey: true },
        (range, context) => {
          toolbar.handlers.link.call(toolbar, !context.format.link);
        },
      );
    }
  }
}
SnowTheme.DEFAULTS = extend(true, {}, BaseTheme.DEFAULTS, {
  modules: {
    toolbar: {
      handlers: {
        link(value) {
          if (value) {
            const range = this.quill.getSelection();
            if (range == null || range.length === 0) return;
            let preview = this.quill.getText(range);
            if (
              /^\S+@\S+\.\S+$/.test(preview) &&
              preview.indexOf('mailto:') !== 0
            ) {
              preview = `mailto:${preview}`;
            }
            const { tooltip } = this.quill.theme;
            tooltip.edit('link', preview);
          } else {
            this.quill.format('link', false);
          }
        },
      },
    },
  },
});

export default SnowTheme;
<commit_msg>Fix tabnabbing vulnerability in Snow theme (#2439)<commit_after>import extend from 'extend';
import Emitter from '../core/emitter';
import BaseTheme, { BaseTooltip } from './base';
import LinkBlot from '../formats/link';
import { Range } from '../core/selection';
import icons from '../ui/icons';

const TOOLBAR_CONFIG = [
  [{ header: ['1', '2', '3', false] }],
  ['bold', 'italic', 'underline', 'link'],
  [{ list: 'ordered' }, { list: 'bullet' }],
  ['clean'],
];

class SnowTooltip extends BaseTooltip {
  constructor(quill, bounds) {
    super(quill, bounds);
    this.preview = this.root.querySelector('a.ql-preview');
  }

  listen() {
    super.listen();
    this.root.querySelector('a.ql-action').addEventListener('click', event => {
      if (this.root.classList.contains('ql-editing')) {
        this.save();
      } else {
        this.edit('link', this.preview.textContent);
      }
      event.preventDefault();
    });
    this.root.querySelector('a.ql-remove').addEventListener('click', event => {
      if (this.linkRange != null) {
        const range = this.linkRange;
        this.restoreFocus();
        this.quill.formatText(range, 'link', false, Emitter.sources.USER);
        delete this.linkRange;
      }
      event.preventDefault();
      this.hide();
    });
    this.quill.on(
      Emitter.events.SELECTION_CHANGE,
      (range, oldRange, source) => {
        if (range == null) return;
        if (range.length === 0 && source === Emitter.sources.USER) {
          const [link, offset] = this.quill.scroll.descendant(
            LinkBlot,
            range.index,
          );
          if (link != null) {
            this.linkRange = new Range(range.index - offset, link.length());
            const preview = LinkBlot.formats(link.domNode);
            this.preview.textContent = preview;
            this.preview.setAttribute('href', preview);
            this.show();
            this.position(this.quill.getBounds(this.linkRange));
            return;
          }
        } else {
          delete this.linkRange;
        }
        this.hide();
      },
    );
  }

  show() {
    super.show();
    this.root.removeAttribute('data-mode');
  }
}
SnowTooltip.TEMPLATE = [
  '',
  '<input type="text" data-formula="e=mc^2" data-link="https://quilljs.com" data-video="Embed URL">',
  '',
  '',
].join('');

class SnowTheme extends BaseTheme {
  constructor(quill, options) {
    if (
      options.modules.toolbar != null &&
      options.modules.toolbar.container == null
    ) {
      options.modules.toolbar.container = TOOLBAR_CONFIG;
    }
    super(quill, options);
    this.quill.container.classList.add('ql-snow');
  }

  extendToolbar(toolbar) {
    toolbar.container.classList.add('ql-snow');
    this.buildButtons(toolbar.container.querySelectorAll('button'), icons);
    this.buildPickers(toolbar.container.querySelectorAll('select'), icons);
    this.tooltip = new SnowTooltip(this.quill, this.options.bounds);
    if (toolbar.container.querySelector('.ql-link')) {
      this.quill.keyboard.addBinding(
        { key: 'k', shortKey: true },
        (range, context) => {
          toolbar.handlers.link.call(toolbar, !context.format.link);
        },
      );
    }
  }
}
SnowTheme.DEFAULTS = extend(true, {}, BaseTheme.DEFAULTS, {
  modules: {
    toolbar: {
      handlers: {
        link(value) {
          if (value) {
            const range = this.quill.getSelection();
            if (range == null || range.length === 0) return;
            let preview = this.quill.getText(range);
            if (
              /^\S+@\S+\.\S+$/.test(preview) &&
              preview.indexOf('mailto:') !== 0
            ) {
              preview = `mailto:${preview}`;
            }
            const { tooltip } = this.quill.theme;
            tooltip.edit('link', preview);
          } else {
            this.quill.format('link', false);
          }
        },
      },
    },
  },
});

export default SnowTheme;
<|endoftext|>