<commit_before>"use strict";

var fs      = require("fs"),
  path      = require("path"),
  util      = require("util"),
  callsites = require("./error-callsites"),
  styles    = require("./styles"),
  base      = process.cwd(),
  config;

config = setConfig();

module.exports = Logger;
module.exports.setConfig = setConfig;

function Logger (name) {
    if (!(this instanceof Logger)) return new Logger(name);

    var self = this;

    this.style = [];
    this.showStackTrace = config.show_stack_trace;

    self.print = function print (style, level, msg /*, ..., ...*/){
        var args = Array.prototype.slice.call(arguments, 2),
          showStackTrace = self.showStackTrace,
          log = {},
          stacktrace;

        self.showStackTrace = config.show_stack_trace;

        log.level = level;
        log.date = new Date();

        if (!name) {
            stacktrace = getStackTrace(msg);
            log.name = formatName(stacktrace[0]);
        } else {
            log.name = String(name);
        }
        log.stacktrace = showStackTrace ? (stacktrace || getStackTrace(msg)) : [];
        log.message = util.format.apply(util, args);

        self.toConsole(log, style);

        process.nextTick(self.toLogFile.bind(log));

    };

    self.debug = self.print.bind(self, ["bold", "light_gray"], "debug");

    self.log = self.print.bind(self, ["bold", "light_gray"], "log");

    self.info = self.print.bind(self, ["bold", "blue"], "info");

    self.warn = self.print.bind(self, ["bold", "yellow"], "warn");

    self.success = self.print.bind(self, ["bold", "green"], "success");

    self.error = self.print.bind(self, ["bold", "red"], "error");

    self.toConsole = function toConsole (log, style){
        var toWrite = [
            " " + applyStyle(log.name, style),
            " " + log.message + "\n",
          ],
          symbols = ["├─", "└─"];

        if (log.stacktrace.length) {
            style = style ?
              style.filter(function (s){ return s !== "bold";}) : [];
            var marker = applyStyle("  %s %s ", style),
              trace = "%s:%s <%s>()",
              col = process.stdout.columns || 80;
            for (let i = 0, len = log.stacktrace.length, mksize = 8, line, stack; i < len; i++) {
                if (i === config.stack_size_limit) {
                    toWrite.push(util.format(marker + "more %s\n", symbols[1], "...", len - i));
                    break;
                }
                stack = log.stacktrace[i];
                line = util.format(trace, stack.filename, stack.line, stack.function);
                if (col < line.length + mksize)
                    line = "..." + line.slice(0 - (col - mksize - 3));
                toWrite.push(util.format(marker, symbols[1 - (i < len -1)], stack.pos) + line + "\n");
            }
        }

        process.stdout.write(toWrite.join(""));
    };

    self.toLogFile = function toLogFile (log){
        // TODO output log to file
    };

    function formatName(stacktrace) {
        return util.format("%s:%s:%s",
            config.app_name,
            path.relative(base, stacktrace.filename),
            stacktrace.line
        );
    }

    function applyStyle(str, arrStyle){
        arrStyle = arrStyle || [];

        arrStyle.forEach(function (s){
            str = styles[s][0] + str + styles[s][1];
        });
        return str + styles.normal[0];
    };

    function getStackTrace(err) {
        var rm = true,
          stack = callsites(err instanceof Error ? err : new Error()),
          lastIndex;

        stack = stack
          .filter(function (st){
              return !(rm = rm && st.getFileName() === __filename);
          });

        lastIndex = stack.length - 1;

        stack = stack.map(function (st, index){
              return {
                  "pos": lastIndex - index,
                  "filename": st.getFileName(),
                  "line": st.getLineNumber(),
                  "function": st.getFunctionName() || "anonymous",
              };
          });

        return stack;
    }
};

Object.keys(styles)
  .forEach(function (key){
      Object.defineProperty(Logger.prototype, key, {
          get: function (){
              this.style.push(key);
              return this;
          }
      });
  });

Object.defineProperties(Logger.prototype, {
    stack: {
        get: function (){
            this.showStackTrace = true;
            return this;
        }
    },
    nostack: {
        get: function (){
            this.showStackTrace = false;
            return this;
        }
    },
});

function setConfig(opt) {
    var opt = opt || {},
      def = {
        app_name: path.basename(base),
        stack_size_limit: 10,
        show_stack_trace: false,
        log_directory: null,
        max_log_size: "5M",
        file_log_prefix: null,
      },
      keys = Object.keys(def);

    config = config || def;

    Object.keys(opt)
      .filter(function (key){
          return keys.indexOf(key) > -1;
      })
      .forEach(function (key){
          config[key] = opt[key];
      });

    return config;
}
<commit_msg>Added trace<commit_after>"use strict";

var fs      = require("fs"),
  path      = require("path"),
  util      = require("util"),
  callsites = require("./error-callsites"),
  styles    = require("./styles"),
  base      = process.cwd(),
  config;

config = setConfig();

module.exports = Logger;
module.exports.setConfig = setConfig;

function Logger (name) {
    if (!(this instanceof Logger)) return new Logger(name);

    var self = this;

    this.style = [];
    this.showStackTrace = config.show_stack_trace;

    self.print = function print (style, level, msg /*, ..., ...*/){
        var args = Array.prototype.slice.call(arguments, 2),
          showStackTrace = self.showStackTrace || level == 'trace',
          log = {},
          stacktrace;

        self.showStackTrace = config.show_stack_trace;

        log.level = level;
        log.date = new Date();

        if (!name) {
            stacktrace = getStackTrace(msg);
            log.name = formatName(stacktrace[0]);
        } else {
            log.name = String(name);
        }
        log.stacktrace = showStackTrace ? (stacktrace || getStackTrace(msg)) : [];
        log.message = util.format.apply(util, args);

        self.toConsole(log, style);

        process.nextTick(self.toLogFile.bind(log));

    };

    self.debug = self.print.bind(self, ["bold", "light_gray"], "debug");

    self.log = self.print.bind(self, ["bold", "light_gray"], "log");

    self.info = self.print.bind(self, ["bold", "blue"], "info");

    self.warn = self.print.bind(self, ["bold", "yellow"], "warn");

    self.success = self.print.bind(self, ["bold", "green"], "success");

    self.error = self.print.bind(self, ["bold", "red"], "error");

    self.trace = self.print.bind(self, ["bold", "red"], "trace");

    self.toConsole = function toConsole (log, style){
        var toWrite = [
            " " + applyStyle(log.name, style),
            " " + log.message + "\n",
          ],
          symbols = ["├─", "└─"];

        if (log.stacktrace.length) {
            style = style ?
              style.filter(function (s){ return s !== "bold";}) : [];
            var marker = applyStyle("  %s %s ", style),
              trace = "%s:%s <%s>()",
              col = process.stdout.columns || 80;
            for (let i = 0, len = log.stacktrace.length, mksize = 8, line, stack; i < len; i++) {
                if (i === config.stack_size_limit) {
                    toWrite.push(util.format(marker + "more %s\n", symbols[1], "...", len - i));
                    break;
                }
                stack = log.stacktrace[i];
                line = util.format(trace, stack.filename, stack.line, stack.function);
                if (col < line.length + mksize)
                    line = "..." + line.slice(0 - (col - mksize - 3));
                toWrite.push(util.format(marker, symbols[1 - (i < len -1)], stack.pos) + line + "\n");
            }
        }

        process.stdout.write(toWrite.join(""));
    };

    self.toLogFile = function toLogFile (log){
        // TODO output log to file
    };

    function formatName(stacktrace) {
        return util.format("%s:%s:%s",
            config.app_name,
            path.relative(base, stacktrace.filename),
            stacktrace.line
        );
    }

    function applyStyle(str, arrStyle){
        arrStyle = arrStyle || [];

        arrStyle.forEach(function (s){
            str = styles[s][0] + str + styles[s][1];
        });
        return str + styles.normal[0];
    };

    function getStackTrace(err) {
        var rm = true,
          stack = callsites(err instanceof Error ? err : new Error()),
          lastIndex;

        stack = stack
          .filter(function (st){
              return !(rm = rm && st.getFileName() === __filename);
          });

        lastIndex = stack.length - 1;

        stack = stack.map(function (st, index){
              return {
                  "pos": lastIndex - index,
                  "filename": st.getFileName(),
                  "line": st.getLineNumber(),
                  "function": st.getFunctionName() || "anonymous",
              };
          });

        return stack;
    }
};

Object.keys(styles)
  .forEach(function (key){
      Object.defineProperty(Logger.prototype, key, {
          get: function (){
              this.style.push(key);
              return this;
          }
      });
  });

Object.defineProperties(Logger.prototype, {
    stack: {
        get: function (){
            this.showStackTrace = true;
            return this;
        }
    },
    nostack: {
        get: function (){
            this.showStackTrace = false;
            return this;
        }
    }
});

function setConfig(opt) {
    var opt = opt || {},
      def = {
        app_name: path.basename(base),
        stack_size_limit: 10,
        show_stack_trace: false,
        log_directory: null,
        max_log_size: "5M",
        file_log_prefix: null,
      },
      keys = Object.keys(def);

    config = config || def;

    Object.keys(opt)
      .filter(function (key){
          return keys.indexOf(key) > -1;
      })
      .forEach(function (key){
          config[key] = opt[key];
      });

    return config;
}
<|endoftext|>


<commit_before>const chalk = require('chalk');
const columnify = require('columnify');
const prettyjson = require('prettyjson');

class Logger {
  static chalkOperation(method, path, operationId, desc) {
    let str = Logger.chalkMethod(method) + '\t' + path;
    if (operationId) str += '\n' + chalk.magenta(operationId);
    if (desc) str += '\n' + chalk.gray(desc);
    return str;
  }

  static chalkMethod(method) {
    method = method.toUpperCase();
    if (method === 'GET') return chalk.green(method);
    if (method === 'PUT' || method === 'POST' || method === 'PATCH') return chalk.yellow(method);
    if (method === 'DELETE') return chalk.red(method);
    return method;
  }

  static chalkType(type) {
    type = type || 'string';
    if (type === 'string') return chalk.green(type);
    if (type === 'integer' || type === 'number') return chalk.blue(type);
    if (type === 'boolean') return chalk.yellow(type);
    if (type === 'array' || type === 'object') return chalk.magenta(type);
    return type;
  }

  static chalkCode(code) {
    if (code.startsWith('2')) return chalk.green(code);
    if (code.startsWith('3')) return chalk.yellow(code);
    if (code.startsWith('4')) return chalk.orange(code);
    if (code.startsWith('5')) return chalk.red(code);
  }

  static padString(str, len) {
    while (str.length < len) str += ' ';
    return str;
  }

  static logColumns(cols, options) {
    options = options || {};
    options.columnSplitter = '  ';
    Logger.log(columnify(cols, options));
  }

  static logJSON(json) {
    Logger.log(prettyjson.render(json));
  }

  static logSchema(schema, indent, name) {
    indent = indent || '';
    if (indent.length > 20) return Logger.log('...\n\n')
    let toLog = name ? indent + Logger.padString(name + ': ', 14) + Logger.chalkType(schema.type) : '';
    if (schema.properties) {
      if (toLog) Logger.log(toLog);
      for (let propName in schema.properties) {
        let prop = schema.properties[propName];
        Logger.logSchema(prop, indent + '  ', propName);
      }
    } else if (schema.items) {
      if (toLog) Logger.log(toLog);
      if (schema.items.properties || schema.items.items) {
        Logger.logSchema(schema.items, indent + '  ', 'items')
      } else {
        toLog = toLog || indent + Logger.chalkType('array');
        toLog += '[' + Logger.chalkType(schema.items.type) + ']';
        Logger.log(toLog);
      }
    } else {
      if (toLog) Logger.log(toLog);
    }
  }

  static logParameters(parameters) {
    let paramDescriptions = parameters.map(p => {
      let ret = {parameter: p.name};
      ret.type = Logger.chalkType(p.in === 'body' ? 'object': p.type);
      ret.required = p.required ? chalk.red('yes') : '';
      ret.default = p.default;
      if (p.description) {
        ret.description = chalk.gray(p.description);
      }
      return ret;
    })
    Logger.log(columnify(paramDescriptions, {
      columnSplitter: '  ',
      config: {
        description: {
          maxWidth: 80
        }
      }
    }));
  }

  static log(str) {
    if (str === undefined) str = '';
    console.log(str);
  }
}

module.exports = Logger;
<commit_msg>fix dupe items in schema log<commit_after>const chalk = require('chalk');
const columnify = require('columnify');
const prettyjson = require('prettyjson');

class Logger {
  static chalkOperation(method, path, operationId, desc) {
    let str = Logger.chalkMethod(method) + '\t' + path;
    if (operationId) str += '\n' + chalk.magenta(operationId);
    if (desc) str += '\n' + chalk.gray(desc);
    return str;
  }

  static chalkMethod(method) {
    method = method.toUpperCase();
    if (method === 'GET') return chalk.green(method);
    if (method === 'PUT' || method === 'POST' || method === 'PATCH') return chalk.yellow(method);
    if (method === 'DELETE') return chalk.red(method);
    return method;
  }

  static chalkType(type) {
    type = type || 'string';
    if (type === 'string') return chalk.green(type);
    if (type === 'integer' || type === 'number') return chalk.blue(type);
    if (type === 'boolean') return chalk.yellow(type);
    if (type === 'array' || type === 'object') return chalk.magenta(type);
    return type;
  }

  static chalkCode(code) {
    if (code.startsWith('2')) return chalk.green(code);
    if (code.startsWith('3')) return chalk.yellow(code);
    if (code.startsWith('4')) return chalk.orange(code);
    if (code.startsWith('5')) return chalk.red(code);
  }

  static padString(str, len) {
    while (str.length < len) str += ' ';
    return str;
  }

  static logColumns(cols, options) {
    options = options || {};
    options.columnSplitter = '  ';
    Logger.log(columnify(cols, options));
  }

  static logJSON(json) {
    Logger.log(prettyjson.render(json));
  }

  static logSchema(schema, indent, name) {
    indent = indent || '';
    if (indent.length > 20) return Logger.log('...\n\n')
    let toLog = name ? indent + Logger.padString(name + ': ', 14) + Logger.chalkType(schema.type) : '';
    if (schema.properties) {
      if (toLog) Logger.log(toLog);
      for (let propName in schema.properties) {
        let prop = schema.properties[propName];
        Logger.logSchema(prop, indent + '  ', propName);
      }
    } else if (schema.items) {
      if (schema.items.properties || schema.items.items) {
        if (toLog) Logger.log(toLog);
        Logger.logSchema(schema.items, indent + '  ', 'items')
      } else {
        toLog = toLog || indent + Logger.chalkType('array');
        toLog += '[' + Logger.chalkType(schema.items.type) + ']';
        Logger.log(toLog);
      }
    } else {
      if (toLog) Logger.log(toLog);
    }
  }

  static logParameters(parameters) {
    let paramDescriptions = parameters.map(p => {
      let ret = {parameter: p.name};
      ret.type = Logger.chalkType(p.in === 'body' ? 'object': p.type);
      ret.required = p.required ? chalk.red('yes') : '';
      ret.default = p.default;
      if (p.description) {
        ret.description = chalk.gray(p.description);
      }
      return ret;
    })
    Logger.log(columnify(paramDescriptions, {
      columnSplitter: '  ',
      config: {
        description: {
          maxWidth: 80
        }
      }
    }));
  }

  static log(str) {
    if (str === undefined) str = '';
    console.log(str);
  }
}

module.exports = Logger;
<|endoftext|>


<commit_before>const winston = require('winston');
require('dotenv').config();
require('winston-syslog');

const logger = winston.createLogger({
	level: 'info',
	format: winston.format.combine(
		winston.format.timestamp(),
		winston.format.json(),
	),
	transports: [
		new winston.transports.Console({timestamp: true}),
		new winston.transports.Syslog({
			host: process.env.PAPERTRAIL_HOSTNAME,
			port: parseInt(process.env.PAPERTRAIL_PORT),
		}),
	],
});

module.exports = logger;<commit_msg>disable syslog logger in dev<commit_after>const winston = require('winston');
require('dotenv').config();
require('winston-syslog');

const logger = winston.createLogger({
	level: 'info',
	format: winston.format.combine(
		winston.format.timestamp(),
		winston.format.json(),
	),
	transports: [
		new winston.transports.Console({timestamp: true}),
        ...(
            process.env.PAPERTRAIL_PORT ?
            [
                new winston.transports.Syslog({
                    host: process.env.PAPERTRAIL_HOSTNAME,
                    port: parseInt(process.env.PAPERTRAIL_PORT),
                }),
            ] : []
        ),
    ],
});

module.exports = logger;
<|endoftext|>


<commit_before>/*
 * https://github.com/5orenso
 *
 * Copyright (c) 2014 Øistein Sørensen
 * Licensed under the MIT license.
 */
'use strict';
var when     = require('when'),
    winston  = require('winston'),
    _        = require('underscore'),
    app_path = __dirname + '/../';

var logger = null; // cached

var Logger = function (opt, mock_services) {
    var opts = opt || {};
    mock_services = mock_services || {};

    if (mock_services.logger) {
        logger = mock_services.logger;
    } else if (!logger) {
        logger = new (winston.Logger)({
            transports: [
                // debug and all other levels above is logged to the console.
                new (winston.transports.Console)({ level: 'debug' }),
                // new (winston.transports.File)({ filename: 'somefile.log' })
                // TODO: Setup standard weblogging using files or services.
                // TODO: Add more transporters. See https://github.com/flatiron/winston
            ]
        });
    }

    return {
        log: function () {
            var level = arguments[0];
            var msg = [];
            var meta = null;
            for (var i = 1, l = arguments.length; i < l; i++) {
                if (_.isString(arguments[i]) || _.isNumber(arguments[i])) {
                    msg.push(arguments[i]);
                } else if (_.isObject(arguments[i]) && !meta) {
                    meta = arguments[i];
                } else if (_.isObject(arguments[i])) {
                    msg.push(JSON.stringify(arguments[i]));
                }
            }
            return when.promise( function (resolve, reject) {
                var ms = (new Date()).getTime();
                resolve(logger.log(level, ms + ' [' + (opts.workerId || '') + ']' + ': ' + msg.join(' -> '), meta));
            });
        },

        err: function (message) {
            var msg = [];
            var meta = null;
            for (var i = 0, l = arguments.length; i < l; i++) {
                if (_.isString(arguments[i]) || _.isNumber(arguments[i])) {
                    msg.push(arguments[i]);
                } else if (_.isObject(arguments[i]) && !meta) {
                    meta = arguments[i];
                } else if (_.isObject(arguments[i])) {
                    msg.push(JSON.stringify(arguments[i]));
                }
            }
            return when.promise( function (resolve, reject) {
                var ms = (new Date()).getTime();
                resolve(logger.log('error', ms + ' [' + (opts.workerId || '') + ']' + ': ' + msg.join(' -> '), meta));
                // TODO: Should handle errors.
            });
        },

        set: function (key, value) {
            opts[key] = value;
            return true;
        },

        get: function (key) {
            return opts[key];
        }
    };
};

module.exports = Logger;
<commit_msg>Fixed module layout.<commit_after>/*
 * https://github.com/5orenso
 *
 * Copyright (c) 2014 Øistein Sørensen
 * Licensed under the MIT license.
 */
'use strict';
var when     = require('when'),
    winston  = require('winston'),
    _        = require('underscore'),
    appPath  = __dirname + '/../',
    opts,
    logger;

function Logger(opt, mockServices) {
    opts = opt || {};
    mockServices = mockServices || {};

    if (mockServices.logger) {
        logger = mockServices.logger;
    } else if (!logger) {
        logger = new (winston.Logger)({
            transports: [
                // debug and all other levels above is logged to the console.
                new (winston.transports.Console)({ level: 'debug' })
                // new (winston.transports.File)({ filename: 'somefile.log' })
                // TODO: Setup standard weblogging using files or services.
                // TODO: Add more transporters. See https://github.com/flatiron/winston
            ]
        });
    }
}

Logger.prototype.log = function log() {
    var level = arguments[0];
    var msg = [];
    var meta = null;
    for (var i = 1, l = arguments.length; i < l; i++) {
        if (_.isString(arguments[i]) || _.isNumber(arguments[i])) {
            msg.push(arguments[i]);
        } else if (_.isObject(arguments[i]) && !meta) {
            meta = arguments[i];
        } else if (_.isObject(arguments[i])) {
            msg.push(JSON.stringify(arguments[i]));
        }
    }
    return when.promise(function (resolve, reject) {
        var ms = (new Date()).getTime();
        resolve(logger.log(level, ms + ' [' + (opts.workerId || '') + ']' + ': ' + msg.join(' -> '), meta));
    });
};

Logger.prototype.err = function err(message) {
    var msg = [];
    var meta = null;
    for (var i = 0, l = arguments.length; i < l; i++) {
        if (_.isString(arguments[i]) || _.isNumber(arguments[i])) {
            msg.push(arguments[i]);
        } else if (_.isObject(arguments[i]) && !meta) {
            meta = arguments[i];
        } else if (_.isObject(arguments[i])) {
            msg.push(JSON.stringify(arguments[i]));
        }
    }
    return when.promise(function (resolve, reject) {
        var ms = (new Date()).getTime();
        resolve(logger.log('error', ms + ' [' + (opts.workerId || '') + ']' + ': ' + msg.join(' -> '), meta));
        // TODO: Should handle errors.
    });
};

Logger.prototype.set = function set(key, value) {
    opts[key] = value;
    return true;
};

Logger.prototype.get = function get(key) {
    return opts[key];
};

module.exports = Logger;
<|endoftext|>


<commit_before>var templates             = require('./templates');
var xpath                 = require('xpath');
var DOMParser             = require('xmldom').DOMParser;
var utils                 = require('./utils');
var trim_xml              = require('./trim_xml');
var signers               = require('./signers');
var InMemoryStore         = require('./store/in_memory_store');
var SessionStore          = require('./store/session_store');
var SessionParticipants   = require('./sessionParticipants');
var zlib                  = require('zlib');
var qs                    = require('querystring');
var xtend                 = require('xtend');
var constants             = require('./constants');

var BINDINGS  = constants.BINDINGS;
var STATUS = constants.STATUS;

// Analyze if we should merge session handler and store
module.exports.logout = function (options) {
  options.sessionParticipants = options.sessionParticipants || new SessionParticipants();
  options.clearIdPSession = options.clearIdPSession || function (cb){ return cb(); };
  options.store = options.store || (options.sloEnabled ? new SessionStore(options) : new InMemoryStore());

  function prepareAndSendLogoutRequest(sessions, req, res, next) {
    // Finished if there are no more session - finish logout
    if (!sessions || sessions.length === 0) { return finalize(req, res, next); }

    options.store.storeState(req, function (err, state) {
      if (err) return next(err);

      // Use session to generate SAML Request
      var logoutRequest = templates.logoutrequest({
        ID: utils.generateUniqueID(),
        IssueInstant: utils.getRoundTripDateFormat(),
        Issuer: options.issuer, // IdP identifier
        NameID: { value: sessions[0].nameId, Format: sessions[0].nameIdFormat },
        SessionIndex: sessions[0].sessionIndex,
        Destination: sessions[0].serviceProviderLogoutURL
      });

      options.destination = sessions[0].serviceProviderLogoutURL;
      options.relayState = state;
      // Send logout request
      prepareAndSendToken(req, res, 'LOGOUT_REQUEST', logoutRequest, options, next);
    });
  }

  function finalize (req, res, next) {
    options.store.getData(req, constants.STORE_KEYS.SP_INIT, function (err, spData) {
      if (err) return next(err);

      var isPartialLogout = options.store.isLogoutFailed(req);

      options.clearIdPSession(function (err) {
        // If there was an issue cleaning the session, reply with partial logout
        if (err) isPartialLogout = true;

        // No data - It was an IdP initated flow
        if (!spData){
          return res.send(200);
        }

        var data = spData.parsedRequest;
        // Data is the parsedSamlRequest - Reply with this information
        var logoutResponse = templates.logoutresponse({
          id:             '_' + utils.generateUniqueID(),
          instant:        utils.generateInstant(),
          inResponseTo:   data.id,
          // TODO: Review if we can remove the options.destination
          destination:    data.serviceProviderLogoutURL || options.destination, // Destination taken from session (match issuer from the LogoutRequest with session serviceProviderId)
          issuer:         options.issuer, // IdP is the Issuer for this LogoutResponse
          samlStatusCode: isPartialLogout ? STATUS.PARTIAL_LOGOUT : STATUS.SUCCESS
        });

        // Update reference to include signature
        options.reference =  constants.ELEMENTS.LOGOUT_RESPONSE.SIGNATURE_LOCATION_PATH;
        options.destination = data.serviceProviderLogoutURL || options.destination;
        // We stored the relay state of the initial request
        options.relayState = spData.relayState;
        prepareAndSendToken(req, res, 'LOGOUT_RESPONSE', logoutResponse, options, next);
      });
    });
  }

  function validateSamlResponse(req, sessions, cb) {
    var SAMLResponse = req.query.SAMLResponse || req.body.SAMLResponse;

    function parseAndValidate(err, buffer) {
      if (err) { return cb(err); }
      var xml = new DOMParser().parseFromString(buffer.toString());
      var parsedResponse = {};

      // status code
      var statusCodes = xml.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'StatusCode');
      var statusCodeXml = statusCodes[0];
      if (statusCodeXml) {
        parsedResponse.status = statusCodeXml.getAttribute('Value');

        // status sub code
        var statusSubCodeXml = statusCodes[1];
        if (statusSubCodeXml) {
          parsedResponse.subCode = statusSubCodeXml.getAttribute('Value');
        }
      }

      // status message
      var samlStatusMsgXml = xml.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'StatusMessage')[0];
      if (samlStatusMsgXml) {
        parsedResponse.message = samlStatusMsgXml.textContent;
      }

      // status detail
      var samlStatusDetailXml = xml.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'StatusDetail')[0];
      if (samlStatusDetailXml) {
        parsedResponse.detail = samlStatusDetailXml.textContent;
      }
      // Issuer
      var issuer = xml.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:assertion', 'Issuer')[0];
      if (issuer) {
        parsedResponse.issuer = issuer.textContent;
      }

      req.parsedSAMLResponse = parsedResponse;
      var sessionParticipants = getSession(sessions, parsedResponse.issuer);

      // validate signature
      try {
        var validationOptions = xtend({signingCert: sessionParticipants.cert}, options);
        utils.validateSignature(req, 'RESPONSE', xml, validationOptions);
      } catch (e) {
        return cb(e);
      }

      cb(null, parsedResponse);
    }

    if (req.body.SAMLResponse || !options.deflate) {
      // HTTP-POST or HTTP-Redirect without deflate encoding
      return parseAndValidate(null, new Buffer(SAMLResponse, 'base64'));
    }

    // Default: HTTP-Redirect with deflate encoding
    zlib.inflateRaw(new Buffer(SAMLResponse, 'base64'), parseAndValidate);
  }

  return function (req, res, next) {
    try {
      // Get SP Active Sessions
      options.sessionParticipants.getAll(function (err, sessions) {
        if (err) { return next(err); }

        // SP Initated flow.
        if (req.query.SAMLRequest || req.body.SAMLRequest) {
          var opts = {
            getCredentials: function getCredentials(issuer) {
              var session = getSession(sessions, issuer);
              if (!session.cert) return;

              return {
                cert: session.cert,
                thumbprint: session.thumbprint
              };
            }
          };

          parseIncomingLogoutRequest(req, req.query.SAMLRequest || req.body.SAMLRequest, opts, function (err, requestData) {
            if (err) { return next(err); }
            if (!requestData.issuer) {
              return next(new Error('SAML Request with no issuer. Issuer is a mandatory element.'));
            }

            var session = getSession(sessions, requestData.issuer);
            requestData.serviceProviderLogoutURL = session.serviceProviderLogoutURL;

            // We should store who requested the logout, so we can reply back with LogoutResponse
            var spData = {
              parsedRequest: requestData,
              relayState: req.query.RelayState || (req.body && req.body.RelayState)
            };

            options.store.storeData(req, constants.STORE_KEYS.SP_INIT, spData, function (err) {
              if (err) { return next(err); }

              // We remove the session from the LogoutRequest Originator.
              // This session is already saved in the store.
              // We should not send a LogoutRequest to that session
              // Only a LogoutResponse when there are no other session participants active
              options.sessionParticipants.remove(requestData.issuer, function (err) {
                if(err) return next(err);

                prepareAndSendLogoutRequest(sessions, req, res, next);
              });
            });
          });

        // Logout flow in progress, incoming SAMLResponse from SP. (Could be SP initiated or IdP initiated)
        } else if (req.query.SAMLResponse || req.body.SAMLResponse) {
          // Verify that the state sent to the SP matches the one returned
          options.store.verifyState(req, req.query.RelayState || (req.body && req.body.RelayState), function (err, ok, state) {
            if (err) { return next(err); }
            if (!ok) { return next(state); }
            // If there are sessions left, keep sending LogoutRequest to Session Participants. If not finish
            validateSamlResponse(req, sessions, function (err, logoutResponse) {
              if (err) { return next(err); }

              // Mark global status as partial logout if a logout does not succeed
              if (logoutResponse.status !== STATUS.SUCCESS) { options.store.setLogoutStatusFailed(req); }

              // LogoutResponse was OK, we remove the session participant from the IdP
              options.sessionParticipants.remove(logoutResponse.issuer, function (err) {
                if(err) return next(err);

                // Continue with next session if any
                prepareAndSendLogoutRequest(sessions, req, res, next);
              });
            });
          });

        // IdP initated - Start flow - In this case we will show a 200 when complete
        } else {
          prepareAndSendLogoutRequest(sessions, req, res, next);
        }
      });
    } catch (e) {
      return next(e);
    }
  };
};

/**
 * Gets the current session assosiated by a Issuer
 */
function getSession(sessions, issuer) {
  if (!issuer) {
    return {};
  }

  var session = sessions.find(function (s) {
    return s.serviceProviderId === issuer;
  });

  return session || {};
}

/**
 * Parse the SP initiated Logout Request.
 * This Logout Request is incoming from the SAML SP into the SAML IdP.
 * @returns {Object} The Logout Request data as a JSON Object
 */
function parseIncomingLogoutRequest(req, samlRequest, options, callback) {
  var type = "LOGOUT_REQUEST";
  utils.parseSamlRequest(req, samlRequest, type, options, function (err, logoutRequestDom) {
    if (err) { return callback(err); }

    var data = {};
    var issuer = xpath.select(constants.ELEMENTS[type].ISSUER_PATH, logoutRequestDom);
    if (issuer && issuer.length > 0) { data.issuer = issuer[0].textContent; }

    var sessionIndex = xpath.select("//*[local-name(.)='SessionIndex']/text()", logoutRequestDom);
    if (sessionIndex && sessionIndex.length > 0) { data.sessionIndex = sessionIndex[0].textContent; }

    var nameId = xpath.select("//*[local-name(.)='NameID']", logoutRequestDom);
    if (nameId && nameId.length > 0) {
      data.nameId = nameId[0].textContent;
      data.nameIdFormat = nameId[0].getAttribute('Format');
    }

    var destination = logoutRequestDom.documentElement.getAttribute('Destination');
    if (destination) data.destination = destination;

    var id = logoutRequestDom.documentElement.getAttribute('ID');
    if (id) data.id = id;

    var signature = xpath.select(options.signaturePath || constants.ELEMENTS[type].SIGNATURE_VALIDATION_PATH, logoutRequestDom);
    if (signature && signature.length > 0) { data.signature = signature[0].textContent; }

    callback(null, data);
  });
}

function prepareAndSendToken(req, res, element_type, token, options, cb) {
  var type = constants.ELEMENTS[element_type].PROP;

  var send = function (params) {
    if (options.protocolBinding !== BINDINGS.HTTP_REDIRECT) {
      // HTTP-POST
      res.set('Content-Type', 'text/html');
      return res.send(templates.form({
        type:         type,
        callback:     options.destination,
        RelayState:   params.RelayState,
        token:        params[type]
      }));
    }

    // HTTP-Redirect
    var samlResponseUrl = utils.appendQueryString(options.destination, params);
    res.redirect(samlResponseUrl);
  };

  var params = {};
  params[type] = null;
  params.RelayState = options.relayState || '';

  // canonical request
  token = trim_xml(token);

  if (options.protocolBinding !== BINDINGS.HTTP_REDIRECT || !options.deflate) {
    // HTTP-POST or HTTP-Redirect without deflate encoding
    try {
      token = signers.signXml(options, token);
    } catch (err) {
      return cb(err);
    }

    params[type] = new Buffer(token).toString('base64');
    return send(params);
  }

  // Default: HTTP-Redirect with deflate encoding (http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf - section 3.4.4.1)
  zlib.deflateRaw(new Buffer(token), function (err, buffer) {
    if (err) return cb(err);

    params[type] = buffer.toString('base64');

    // construct the Signature: a string consisting of the concatenation of the SAMLResponse,
    // RelayState (if present) and SigAlg query string parameters (each one URLencoded)
    if (params.RelayState === '') {
      // if there is no RelayState value, the parameter should be omitted from the signature computation
      delete params.RelayState;
    }

    params.SigAlg = signers.getSigAlg(options);
    params.Signature = signers.sign(options, qs.stringify(params));

    send(params);
  });
}<commit_msg>Refactor store initialization<commit_after>var templates             = require('./templates');
var xpath                 = require('xpath');
var DOMParser             = require('xmldom').DOMParser;
var utils                 = require('./utils');
var trim_xml              = require('./trim_xml');
var signers               = require('./signers');
var SessionStore          = require('./store/session_store');
var SessionParticipants   = require('./sessionParticipants');
var zlib                  = require('zlib');
var qs                    = require('querystring');
var xtend                 = require('xtend');
var constants             = require('./constants');

var BINDINGS  = constants.BINDINGS;
var STATUS = constants.STATUS;

// Analyze if we should merge session handler and store
module.exports.logout = function (options) {
  options.sessionParticipants = options.sessionParticipants || new SessionParticipants();
  options.clearIdPSession = options.clearIdPSession || function (cb){ return cb(); };
  options.store = options.store || new SessionStore(options);

  function prepareAndSendLogoutRequest(sessions, req, res, next) {
    // Finished if there are no more session - finish logout
    if (!sessions || sessions.length === 0) { return finalize(req, res, next); }

    options.store.storeState(req, function (err, state) {
      if (err) return next(err);

      // Use session to generate SAML Request
      var logoutRequest = templates.logoutrequest({
        ID: utils.generateUniqueID(),
        IssueInstant: utils.getRoundTripDateFormat(),
        Issuer: options.issuer, // IdP identifier
        NameID: { value: sessions[0].nameId, Format: sessions[0].nameIdFormat },
        SessionIndex: sessions[0].sessionIndex,
        Destination: sessions[0].serviceProviderLogoutURL
      });

      options.destination = sessions[0].serviceProviderLogoutURL;
      options.relayState = state;
      // Send logout request
      prepareAndSendToken(req, res, 'LOGOUT_REQUEST', logoutRequest, options, next);
    });
  }

  function finalize (req, res, next) {
    options.store.getData(req, constants.STORE_KEYS.SP_INIT, function (err, spData) {
      if (err) return next(err);

      var isPartialLogout = options.store.isLogoutFailed(req);

      options.clearIdPSession(function (err) {
        // If there was an issue cleaning the session, reply with partial logout
        if (err) isPartialLogout = true;

        // No data - It was an IdP initated flow
        if (!spData){
          return res.send(200);
        }

        var data = spData.parsedRequest;
        // Data is the parsedSamlRequest - Reply with this information
        var logoutResponse = templates.logoutresponse({
          id:             '_' + utils.generateUniqueID(),
          instant:        utils.generateInstant(),
          inResponseTo:   data.id,
          // TODO: Review if we can remove the options.destination
          destination:    data.serviceProviderLogoutURL || options.destination, // Destination taken from session (match issuer from the LogoutRequest with session serviceProviderId)
          issuer:         options.issuer, // IdP is the Issuer for this LogoutResponse
          samlStatusCode: isPartialLogout ? STATUS.PARTIAL_LOGOUT : STATUS.SUCCESS
        });

        // Update reference to include signature
        options.reference =  constants.ELEMENTS.LOGOUT_RESPONSE.SIGNATURE_LOCATION_PATH;
        options.destination = data.serviceProviderLogoutURL || options.destination;
        // We stored the relay state of the initial request
        options.relayState = spData.relayState;
        prepareAndSendToken(req, res, 'LOGOUT_RESPONSE', logoutResponse, options, next);
      });
    });
  }

  function validateSamlResponse(req, sessions, cb) {
    var SAMLResponse = req.query.SAMLResponse || req.body.SAMLResponse;

    function parseAndValidate(err, buffer) {
      if (err) { return cb(err); }
      var xml = new DOMParser().parseFromString(buffer.toString());
      var parsedResponse = {};

      // status code
      var statusCodes = xml.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'StatusCode');
      var statusCodeXml = statusCodes[0];
      if (statusCodeXml) {
        parsedResponse.status = statusCodeXml.getAttribute('Value');

        // status sub code
        var statusSubCodeXml = statusCodes[1];
        if (statusSubCodeXml) {
          parsedResponse.subCode = statusSubCodeXml.getAttribute('Value');
        }
      }

      // status message
      var samlStatusMsgXml = xml.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'StatusMessage')[0];
      if (samlStatusMsgXml) {
        parsedResponse.message = samlStatusMsgXml.textContent;
      }

      // status detail
      var samlStatusDetailXml = xml.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'StatusDetail')[0];
      if (samlStatusDetailXml) {
        parsedResponse.detail = samlStatusDetailXml.textContent;
      }
      // Issuer
      var issuer = xml.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:assertion', 'Issuer')[0];
      if (issuer) {
        parsedResponse.issuer = issuer.textContent;
      }

      req.parsedSAMLResponse = parsedResponse;
      var sessionParticipants = getSession(sessions, parsedResponse.issuer);

      // validate signature
      try {
        var validationOptions = xtend({signingCert: sessionParticipants.cert}, options);
        utils.validateSignature(req, 'RESPONSE', xml, validationOptions);
      } catch (e) {
        return cb(e);
      }

      cb(null, parsedResponse);
    }

    if (req.body.SAMLResponse || !options.deflate) {
      // HTTP-POST or HTTP-Redirect without deflate encoding
      return parseAndValidate(null, new Buffer(SAMLResponse, 'base64'));
    }

    // Default: HTTP-Redirect with deflate encoding
    zlib.inflateRaw(new Buffer(SAMLResponse, 'base64'), parseAndValidate);
  }

  return function (req, res, next) {
    try {
      // Get SP Active Sessions
      options.sessionParticipants.getAll(function (err, sessions) {
        if (err) { return next(err); }

        // SP Initated flow.
        if (req.query.SAMLRequest || req.body.SAMLRequest) {
          var opts = {
            getCredentials: function getCredentials(issuer) {
              var session = getSession(sessions, issuer);
              if (!session.cert) return;

              return {
                cert: session.cert,
                thumbprint: session.thumbprint
              };
            }
          };

          parseIncomingLogoutRequest(req, req.query.SAMLRequest || req.body.SAMLRequest, opts, function (err, requestData) {
            if (err) { return next(err); }
            if (!requestData.issuer) {
              return next(new Error('SAML Request with no issuer. Issuer is a mandatory element.'));
            }

            var session = getSession(sessions, requestData.issuer);
            requestData.serviceProviderLogoutURL = session.serviceProviderLogoutURL;

            // We should store who requested the logout, so we can reply back with LogoutResponse
            var spData = {
              parsedRequest: requestData,
              relayState: req.query.RelayState || (req.body && req.body.RelayState)
            };

            options.store.storeData(req, constants.STORE_KEYS.SP_INIT, spData, function (err) {
              if (err) { return next(err); }

              // We remove the session from the LogoutRequest Originator.
              // This session is already saved in the store.
              // We should not send a LogoutRequest to that session
              // Only a LogoutResponse when there are no other session participants active
              options.sessionParticipants.remove(requestData.issuer, function (err) {
                if(err) return next(err);

                prepareAndSendLogoutRequest(sessions, req, res, next);
              });
            });
          });

        // Logout flow in progress, incoming SAMLResponse from SP. (Could be SP initiated or IdP initiated)
        } else if (req.query.SAMLResponse || req.body.SAMLResponse) {
          // Verify that the state sent to the SP matches the one returned
          options.store.verifyState(req, req.query.RelayState || (req.body && req.body.RelayState), function (err, ok, state) {
            if (err) { return next(err); }
            if (!ok) { return next(state); }
            // If there are sessions left, keep sending LogoutRequest to Session Participants. If not finish
            validateSamlResponse(req, sessions, function (err, logoutResponse) {
              if (err) { return next(err); }

              // Mark global status as partial logout if a logout does not succeed
              if (logoutResponse.status !== STATUS.SUCCESS) { options.store.setLogoutStatusFailed(req); }

              // LogoutResponse was OK, we remove the session participant from the IdP
              options.sessionParticipants.remove(logoutResponse.issuer, function (err) {
                if(err) return next(err);

                // Continue with next session if any
                prepareAndSendLogoutRequest(sessions, req, res, next);
              });
            });
          });

        // IdP initated - Start flow - In this case we will show a 200 when complete
        } else {
          prepareAndSendLogoutRequest(sessions, req, res, next);
        }
      });
    } catch (e) {
      return next(e);
    }
  };
};

/**
 * Gets the current session assosiated by a Issuer
 */
function getSession(sessions, issuer) {
  if (!issuer) {
    return {};
  }

  var session = sessions.find(function (s) {
    return s.serviceProviderId === issuer;
  });

  return session || {};
}

/**
 * Parse the SP initiated Logout Request.
 * This Logout Request is incoming from the SAML SP into the SAML IdP.
 * @returns {Object} The Logout Request data as a JSON Object
 */
function parseIncomingLogoutRequest(req, samlRequest, options, callback) {
  var type = "LOGOUT_REQUEST";
  utils.parseSamlRequest(req, samlRequest, type, options, function (err, logoutRequestDom) {
    if (err) { return callback(err); }

    var data = {};
    var issuer = xpath.select(constants.ELEMENTS[type].ISSUER_PATH, logoutRequestDom);
    if (issuer && issuer.length > 0) { data.issuer = issuer[0].textContent; }

    var sessionIndex = xpath.select("//*[local-name(.)='SessionIndex']/text()", logoutRequestDom);
    if (sessionIndex && sessionIndex.length > 0) { data.sessionIndex = sessionIndex[0].textContent; }

    var nameId = xpath.select("//*[local-name(.)='NameID']", logoutRequestDom);
    if (nameId && nameId.length > 0) {
      data.nameId = nameId[0].textContent;
      data.nameIdFormat = nameId[0].getAttribute('Format');
    }

    var destination = logoutRequestDom.documentElement.getAttribute('Destination');
    if (destination) data.destination = destination;

    var id = logoutRequestDom.documentElement.getAttribute('ID');
    if (id) data.id = id;

    var signature = xpath.select(options.signaturePath || constants.ELEMENTS[type].SIGNATURE_VALIDATION_PATH, logoutRequestDom);
    if (signature && signature.length > 0) { data.signature = signature[0].textContent; }

    callback(null, data);
  });
}

function prepareAndSendToken(req, res, element_type, token, options, cb) {
  var type = constants.ELEMENTS[element_type].PROP;

  var send = function (params) {
    if (options.protocolBinding !== BINDINGS.HTTP_REDIRECT) {
      // HTTP-POST
      res.set('Content-Type', 'text/html');
      return res.send(templates.form({
        type:         type,
        callback:     options.destination,
        RelayState:   params.RelayState,
        token:        params[type]
      }));
    }

    // HTTP-Redirect
    var samlResponseUrl = utils.appendQueryString(options.destination, params);
    res.redirect(samlResponseUrl);
  };

  var params = {};
  params[type] = null;
  params.RelayState = options.relayState || '';

  // canonical request
  token = trim_xml(token);

  if (options.protocolBinding !== BINDINGS.HTTP_REDIRECT || !options.deflate) {
    // HTTP-POST or HTTP-Redirect without deflate encoding
    try {
      token = signers.signXml(options, token);
    } catch (err) {
      return cb(err);
    }

    params[type] = new Buffer(token).toString('base64');
    return send(params);
  }

  // Default: HTTP-Redirect with deflate encoding (http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf - section 3.4.4.1)
  zlib.deflateRaw(new Buffer(token), function (err, buffer) {
    if (err) return cb(err);

    params[type] = buffer.toString('base64');

    // construct the Signature: a string consisting of the concatenation of the SAMLResponse,
    // RelayState (if present) and SigAlg query string parameters (each one URLencoded)
    if (params.RelayState === '') {
      // if there is no RelayState value, the parameter should be omitted from the signature computation
      delete params.RelayState;
    }

    params.SigAlg = signers.getSigAlg(options);
    params.Signature = signers.sign(options, qs.stringify(params));

    send(params);
  });
}<|endoftext|>


<commit_before>var IS_WIN = process.platform.indexOf('win') === 0;
var colors = [
        "000000",
        "800000",
        "008000",
        "808000",
        "000080",
        "800080",
        "008080",
        "c0c0c0",
        "808080",
        "ff0000",
        "00ff00",
        "ffff00",
        "0000ff",
        "ff00ff",
        "00ffff",
        "ffffff",
        "000000",
        "00005f",
        "000087",
        "0000af",
        "0000d7",
        "0000ff",
        "005f00",
        "005f5f",
        "005f87",
        "005faf",
        "005fd7",
        "005fff",
        "008700",
        "00875f",
        "008787",
        "0087af",
        "0087d7",
        "0087ff",
        "00af00",
        "00af5f",
        "00af87",
        "00afaf",
        "00afd7",
        "00afff",
        "00d700",
        "00d75f",
        "00d787",
        "00d7af",
        "00d7d7",
        "00d7ff",
        "00ff00",
        "00ff5f",
        "00ff87",
        "00ffaf",
        "00ffd7",
        "00ffff",
        "5f0000",
        "5f005f",
        "5f0087",
        "5f00af",
        "5f00d7",
        "5f00ff",
        "5f5f00",
        "5f5f5f",
        "5f5f87",
        "5f5faf",
        "5f5fd7",
        "5f5fff",
        "5f8700",
        "5f875f",
        "5f8787",
        "5f87af",
        "5f87d7",
        "5f87ff",
        "5faf00",
        "5faf5f",
        "5faf87",
        "5fafaf",
        "5fafd7",
        "5fafff",
        "5fd700",
        "5fd75f",
        "5fd787",
        "5fd7af",
        "5fd7d7",
        "5fd7ff",
        "5fff00",
        "5fff5f",
        "5fff87",
        "5fffaf",
        "5fffd7",
        "5fffff",
        "870000",
        "87005f",
        "870087",
        "8700af",
        "8700d7",
        "8700ff",
        "875f00",
        "875f5f",
        "875f87",
        "875faf",
        "875fd7",
        "875fff",
        "878700",
        "87875f",
        "878787",
        "8787af",
        "8787d7",
        "8787ff",
        "87af00",
        "87af5f",
        "87af87",
        "87afaf",
        "87afd7",
        "87afff",
        "87d700",
        "87d75f",
        "87d787",
        "87d7af",
        "87d7d7",
        "87d7ff",
        "87ff00",
        "87ff5f",
        "87ff87",
        "87ffaf",
        "87ffd7",
        "87ffff",
        "af0000",
        "af005f",
        "af0087",
        "af00af",
        "af00d7",
        "af00ff",
        "af5f00",
        "af5f5f",
        "af5f87",
        "af5faf",
        "af5fd7",
        "af5fff",
        "af8700",
        "af875f",
        "af8787",
        "af87af",
        "af87d7",
        "af87ff",
        "afaf00",
        "afaf5f",
        "afaf87",
        "afafaf",
        "afafd7",
        "afafff",
        "afd700",
        "afd75f",
        "afd787",
        "afd7af",
        "afd7d7",
        "afd7ff",
        "afff00",
        "afff5f",
        "afff87",
        "afffaf",
        "afffd7",
        "afffff",
        "d70000",
        "d7005f",
        "d70087",
        "d700af",
        "d700d7",
        "d700ff",
        "d75f00",
        "d75f5f",
        "d75f87",
        "d75faf",
        "d75fd7",
        "d75fff",
        "d78700",
        "d7875f",
        "d78787",
        "d787af",
        "d787d7",
        "d787ff",
        "d7af00",
        "d7af5f",
        "d7af87",
        "d7afaf",
        "d7afd7",
        "d7afff",
        "d7d700",
        "d7d75f",
        "d7d787",
        "d7d7af",
        "d7d7d7",
        "d7d7ff",
        "d7ff00",
        "d7ff5f",
        "d7ff87",
        "d7ffaf",
        "d7ffd7",
        "d7ffff",
        "ff0000",
        "ff005f",
        "ff0087",
        "ff00af",
        "ff00d7",
        "ff00ff",
        "ff5f00",
        "ff5f5f",
        "ff5f87",
        "ff5faf",
        "ff5fd7",
        "ff5fff",
        "ff8700",
        "ff875f",
        "ff8787",
        "ff87af",
        "ff87d7",
        "ff87ff",
        "ffaf00",
        "ffaf5f",
        "ffaf87",
        "ffafaf",
        "ffafd7",
        "ffafff",
        "ffd700",
        "ffd75f",
        "ffd787",
        "ffd7af",
        "ffd7d7",
        "ffd7ff",
        "ffff00",
        "ffff5f",
        "ffff87",
        "ffffaf",
        "ffffd7",
        "ffffff",
        "080808",
        "121212",
        "1c1c1c",
        "262626",
        "303030",
        "3a3a3a",
        "444444",
        "4e4e4e",
        "585858",
        "606060",
        "666666",
        "767676",
        "808080",
        "8a8a8a",
        "949494",
        "9e9e9e",
        "a8a8a8",
        "b2b2b2",
        "bcbcbc",
        "c6c6c6",
        "d0d0d0",
        "dadada",
        "e4e4e4",
        "eeeeee"
    ]
    .map(function(hex) {
        var r = parseInt(hex.slice(0, 2), 16);
        var g = parseInt(hex.slice(2, 4), 16);
        var b = parseInt(hex.slice(4, 6), 16);
        return [r, g, b];
    });

var x256 = function(r, g, b) {
    var c = Array.isArray(r) ? r : [r, g, b];
    var best = null;

    for (var i = 0; i < colors.length; i++) {
        var d = distance(colors[i], c)
        if (!best || d <= best.distance) {
            best = {
                distance: d,
                index: i
            };
        }
    }

    return best.index;
};
x256.colors = colors;

function distance(a, b) {
    return Math.sqrt(
        Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2)
    )
}

String.prototype.segment = function(length, offset) {
    "use strict";
    var output = [];
    // Make sure offset < length, then move the pointer to that position
    var position = offset % length - length;
    for (; position < this.length;) output.push(this.slice(Math.max(0, position), position += length));
    return output;
}

function lolcat(string) {
    if (IS_WIN) {
      return string;
    }

    var segmentLength = 11;
    var spread = 3;
    var freq = 0.1;
    var seed = Math.random() * 20;
    // Make tabs display as spaces
    var output = string.replace(/\t/g, "        ").split("\n");
    var color, string;
    for (var line = 0; line < output.length; line++) {
        output[line] = output[line].segment(segmentLength, line * spread + 2);
        for (var segment = 0; segment < output[line].length; segment++) {
            string = output[line][segment];
            color = rainbow(freq, (segment - line - seed) * spread);
            output[line][segment] = color + 'm' + string;
        }
        output[line] = output[line].join("");
    }
    return output.join("\n");
}

// Ported directly from lol.rb:rainbow
function rainbow(freq, i) {
    var red = Math.round(Math.sin(freq * i + 0) * 127 + 128);
    var green = Math.round(Math.sin(freq * i + 2 * Math.PI / 3) * 127 + 128);
    var blue = Math.round(Math.sin(freq * i + 4 * Math.PI / 3) * 127 + 128);
    var ix = x256(red, green, blue);
    return '\x1b[38;5;' + ix;
}

module.exports = lolcat;<commit_msg>rm lolcat.js<commit_after><|endoftext|>


<commit_before>import React from 'react';
import {without, includes, union, isNull, find} from 'lodash';
import styles from './multi-selector.css';

function DefaultItemComponent(props) {
	const item = props.item;
	const getItemTitle = props.getItemTitle;
	const selectedItems = props.selectedItems;
	const selected = includes(selectedItems, item);

	return (
		<div title={`${getItemTitle(item)}`}>
			<div
				className={`${styles['cpr-multi-selector-item__icon']} ${selected ? `cps-bg-primary-green ${styles[`cpr-multi-selector-item__icon--selected`]}` : ""}`}>

			<div className={`${styles['cpr-multi-selector-item__title']}`}>{`${getItemTitle(item)}`}

	)
}

DefaultItemComponent.propTypes = {
	item: React.PropTypes.any.isRequired,
	getItemTitle: React.PropTypes.func,
	selectedItems: React.PropTypes.array.isRequired
};

const MultiSelector = React.createClass({
	propTypes: {
		items: React.PropTypes.array.isRequired,
		initialSelectedItems: React.PropTypes.array,
		onInputChange: React.PropTypes.func,
		onChange: React.PropTypes.func,
		getItemTitle: React.PropTypes.func,
		ItemComponent: React.PropTypes.oneOfType(
			[React.PropTypes.element, React.PropTypes.func]
		),
		placeholder: React.PropTypes.string,
		pillPlaceholder: React.PropTypes.string,
		maxLength: React.PropTypes.number,
		noRestrict: React.PropTypes.bool,
		pressEnterToAddPhrase: React.PropTypes.string,
		noResultsPhrase: React.PropTypes.string,
		customCSSClass: React.PropTypes.string,
		hasError: React.PropTypes.bool,
		color: React.PropTypes.string,
		closeOnSelect: React.PropTypes.bool,
	},

	componentWillMount: function() {
		document.addEventListener('click', this.state.close);
	},

	componentWillUnmount: function() {
		document.removeEventListener('click', this.state.close);
	},

	getInitialState: function() {
		return {
			selectedItems: this.props.initialSelectedItems || [],
			mouseIndex: null,
			mouseActive: true,
			mouseFunc: null,
			dialogDisplayed: false,
			activeIndex: null,
			searchValue: '',
			close: (e) => {
				const eventOccurredInsideOfThisComponent = this.el ? this.el.contains(e.target) : false;
				if (!eventOccurredInsideOfThisComponent) {
					setTimeout(() => {
						if (this.state.dialogDisplayed && this.isMounted()) {
							this.props.onBlur && this.props.onBlur();
							this.setState({
								dialogDisplayed: false,
								searchValue: ''
							});
						}
					})
				}
			},
			invalid: false,
		}
	},

	componentWillReceiveProps(nextProps) {
		this.setState({
			selectedItems: nextProps.initialSelectedItems || []
		})
	},

	displayDialog: function(e) {
		if (!this.state.dialogDisplayed)
			this.props.onFocus && this.props.onFocus();
		this.setState({
			dialogDisplayed: true
		})
	},

	removeItem: function(item, e) {
		this.setState({
			selectedItems: without(this.state.selectedItems, item)
		});

		setTimeout(this.triggerItemChange);
	},

	getItemTitle: function(item) {
		return item.label;
	},

	setActiveIndex(index) {
		this.setState({
			activeIndex: index,
			mouseIndex: null,
		}, () => {
			if (!isNull(this.state.activeIndex) && !!this.searchItems[this.state.activeIndex]) {
				this.searchItems[this.state.activeIndex].scrollIntoView();
				if (this.state.mouseActive) {
					this.setState({
						mouseActive: false,
						mouseFunc: () => {
							this.setState({
								mouseActive: true
							})
							document.removeEventListener("mousemove", this.state.mouseFunc);
						}
					}, () => {
						document.addEventListener("mousemove", this.state.mouseFunc);
					})
				}
			}
		})
	},

	handleChange: function(e) {
		e.persist();
		if (this.state.invalid && this.props.validate) {
			this.setState({
				invalid: !this.props.validate(e.target.value),
			});
		}
		this.setState({
			disabled: this.props.disableInput ? this.props.disableInput(e.target.value) : false,
		}, () => {
			this.inputChange(e.target.value);
		});
	},

	inputChange: function(newVal) {
		this.props.onInputChange && this.props.onInputChange(newVal);

		this.setState({
			searchValue: newVal
		});
	},

	keyDown: function(e) {
		const keycode = e.which;
		const activeIndex = this.state.activeIndex;
		const filterItems = this.getFilterItems(this.props.items);

		if (isNull(activeIndex) && filterItems.length !== 0) {
			this.setActiveIndex(0);
		} else if (filterItems.length === 0) {
			this.setActiveIndex(null);
		}

		if (keycode === 13) e.preventDefault();
		if(keycode === 40) { // press down key
			if (isNull(activeIndex) && filterItems.length !== 0) {
				return this.setActiveIndex(0);
			} else {
				if(activeIndex < filterItems.length - 1) {
					return this.setActiveIndex(activeIndex + 1);
				}
			}
		} else if(keycode === 38) { // press up key
			if(!activeIndex) {
				return this.setActiveIndex(0);
			} else {
				if(activeIndex > 0) {
					return this.setActiveIndex(activeIndex - 1);
				}
			}
		} else if(keycode === 13) { // press enter key
			if(!isNull(activeIndex) && filterItems.length !==0) {
				return this.selectItem(filterItems[activeIndex], e);
			} else if(this.props.noRestrict && e.currentTarget.value) {
				// if the noRestrict prop is true it adds the input as a string to the selected items on enter
				e.persist();
				return this.validateInput(e.currentTarget.value, e);
			}
		} else if(keycode === 27) { // press escape key
			return this.setState({
				activeIndex: null,
				dialogDisplayed: false
			});
		} else if(keycode === 9) { //tab key
			this.setState({
				dialogDisplayed: false,
			});
		}
	},

	triggerItemChange: function() {
		if (this.props.onChange) {
			this.props.onChange.call(null, this.state.selectedItems);
		}
	},

	getSelectedClass: function(item) {
		return includes(this.state.selectedItems, item) ? '+selected' : '';
	},

	getActiveClass: function(index) {
		return this.state.activeIndex === index || this.state.mouseIndex === index
			? styles['cpr-multi-selector-item--highlighted']
			: '';
	},

	getFilterItems: function(items = []) {
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;

		return items
			.filter((item) => {
				return getItemTitle(item).toLowerCase().indexOf(this.state.searchValue.toLowerCase()) > -1;
			})
	},

	getSearchItems: function(items = []) {
		let ItemComponent = this.props.ItemComponent || DefaultItemComponent;
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;
		let filterItems = this.getFilterItems(items);

		// Show a message that user can press enter to add new item
		if (filterItems.length === 0) {
			if (this.props.noRestrict && this.state.searchValue) {
				const pressEnterToAddPhrase = this.props.pressEnterToAddPhrase || 'Press Enter to add';
				return (
					<div className={`${styles['cpr-multi-selector-item']}`}>{pressEnterToAddPhrase} "{this.state.searchValue}"

				)
			} else {
				const noResultsPhrase = this.props.noResultsPhrase || 'No items found.';
				return (
					<div className={`${styles['cpr-multi-selector-item']}`}>{noResultsPhrase}

				)
			}
		}

		// If noRestrict & the search term doesn't have an exact match, append an additional "result" for the new item
		// This is to allow adding of new items when the search term has matching filtered items but not an exact match
		if (this.props.noRestrict && this.state.searchValue
		&& !find(filterItems, (item) => getItemTitle(item).toLowerCase() === this.state.searchValue.toLowerCase())) {
			filterItems.push(this.state.searchValue);
		}

		if (this.props.maxSearchItems) {
			filterItems.length = filterItems.length > this.props.maxSearchItems
				? this.props.maxSearchItems
				: filterItems.length;
		}

		return filterItems.map((item, index) => {
			return (

 {
						if (this.searchItems) {
							this.searchItems[index] = ref;
						} else {
							this.searchItems = [];
							this.searchItems[index] = ref;
						}
					}}
					onMouseOver={() => {
						if (this.state.mouseActive) {
							this.setState({
								mouseIndex: index
							})
						}
					}}
					className={`${styles['cpr-multi-selector-item']} ${this.getSelectedClass(item)} ${this.getActiveClass(index)}`}
					onClick={this.selectItem.bind(this, item)}>
					<ItemComponent item={item} selectedItems={this.state.selectedItems} getItemTitle={getItemTitle}/>

			)
		})
	},

	selectItem: function(item, e) {
		let selectedItems = this.state.selectedItems;

		if(includes(selectedItems, item)) {
			this.setState({
				selectedItems: without(selectedItems, item),
				invalid: false,
				dialogDisplayed: !this.props.closeOnSelect,
			}, this.triggerItemChange);
		} else {
			this.setState({
				selectedItems: union(selectedItems, [ item ]),
				invalid: false,
				dialogDisplayed: !this.props.closeOnSelect,
			}, this.triggerItemChange);
		}

		if (e && e.currentTarget) {
			e.currentTarget.value = '';
			this.inputChange('');
		}
	},

	validateInput: function(input, e) {
		if (this.state.disabled) return;
		if (this.props.validate) {
			const valid = this.props.validate(input);
			if (valid) this.selectItem(input, e);
			else this.setState({invalid: true});
		} else {
			this.selectItem(input, e);
		}
	},

	positionDialog: function() {
		setTimeout(() => {
			let el = this.el;
			let height = el.clientHeight;
			let dialog = el.querySelector(`.${styles['cpr-multi-selector__dialog']}`);

			if (dialog) {
				dialog.style.top = (height + 1) + 'px';
				el.querySelector(`.${styles['cpr-multi-selector__dialog__input']}`).focus();
			}
		}, 100);
	},

	prevent: function(e){
		if(e.which === 13) e.preventDefault();
	},

	render: function() {
		//Get getItemTitle is the function that should be passed in to decide what `pill` will display on selection.
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;

		let pills = this.state.selectedItems
			.map((item, i) => {
				return (
					<div
						key={i}
						className={`${styles['cpr-multi-selector__pill']} ${styles[`cpr-multi-selector__pill--${this.props.color || "green"}`]}`}
						title={`${getItemTitle(item)}`}>

							{getItemTitle(item)}

						<div className={`${styles['cpr-multi-selector__pill__close']}`}>
							 {
									e.stopPropagation();
									this.removeItem(item)
								}}
								className="cps-icon cps-icon-sm-neg">

				);
			});

		let dialog;

		if (this.state.dialogDisplayed) {
			let placeholder = this.props.placeholder ? this.props.placeholder : "Type a collaborators name...";
			let maxLength = this.props.maxLength;
			dialog = (
				<div className={`${styles['cpr-multi-selector__dialog']} depth-z2`} style={{}}>
					<div
						className={`${this.state.invalid ? "cps-has-error" : ""}`}
						style={{padding: "16px", borderBottom: "1px solid #E9E9E9"}}>
						<input
							onChange={this.handleChange}
							onKeyDown={this.keyDown}
							className={`cps-form-control ${styles['cpr-multi-selector__dialog__input']}`}
							placeholder={placeholder}
							{...(maxLength ? {maxLength} : {})}/>
						{this.state.invalid &&
							{this.props.invalidMsg}}

					<div
						style={this.props.dialogHeight ? {maxHeight: this.props.dialogHeight} : {}}
						className={`${styles['cpr-multi-selector__dialog__items']}`}>
						{this.getSearchItems(this.props.items)}

						{(this.state.searchValue && this.props.doneButton) &&
							<div
								style={{padding: "8px 16px", borderTop: "1px solid rgb(233, 233, 233)"}}
								className={`cps-bg-gray-3 ${styles['cpr-multi-selector__done']}`}>
								<button
									disabled={this.state.diabled}
									onClick={this.validateInput.bind(this, this.state.searchValue)}
									className={`cps-btn +primary ${this.state.disabled ? "+disabled" : ""}`}>
									DONE
								</button>

}

			)

			this.positionDialog();
		}

		return (

 { if (el) this.el = el }}
				className={`${styles['cpr-multi-selector']} ${this.state.dialogDisplayed ? styles['cpr-multi-selector--active'] : ''} ${this.props.customCSSClass || ''}`}>
				<input
					type="input" className={`${styles['cpr-multi-selector__hidden-input']} ${this.props.hasError ? styles['cpr-multi-selector__has-error'] : ''}`}
					onFocus={this.displayDialog}/>
				<div
					onClick={this.displayDialog}
					className={`${styles['cpr-multi-selector__main-input']} cps-form-control`}>
					{ pills && pills.length
						? pills
						:
{this.props.pillPlaceholder}

					}

				{dialog}

		)
	}
});

if (window && !window.MultiSelector) window.MultiSelector = MultiSelector;

export default MultiSelector;
<commit_msg>use setState callback to trigger item change on remove (#137)<commit_after>import React from 'react';
import {without, includes, union, isNull, find} from 'lodash';
import styles from './multi-selector.css';

function DefaultItemComponent(props) {
	const item = props.item;
	const getItemTitle = props.getItemTitle;
	const selectedItems = props.selectedItems;
	const selected = includes(selectedItems, item);

	return (
		<div title={`${getItemTitle(item)}`}>
			<div
				className={`${styles['cpr-multi-selector-item__icon']} ${selected ? `cps-bg-primary-green ${styles[`cpr-multi-selector-item__icon--selected`]}` : ""}`}>

			<div className={`${styles['cpr-multi-selector-item__title']}`}>{`${getItemTitle(item)}`}

	)
}

DefaultItemComponent.propTypes = {
	item: React.PropTypes.any.isRequired,
	getItemTitle: React.PropTypes.func,
	selectedItems: React.PropTypes.array.isRequired
};

const MultiSelector = React.createClass({
	propTypes: {
		items: React.PropTypes.array.isRequired,
		initialSelectedItems: React.PropTypes.array,
		onInputChange: React.PropTypes.func,
		onChange: React.PropTypes.func,
		getItemTitle: React.PropTypes.func,
		ItemComponent: React.PropTypes.oneOfType(
			[React.PropTypes.element, React.PropTypes.func]
		),
		placeholder: React.PropTypes.string,
		pillPlaceholder: React.PropTypes.string,
		maxLength: React.PropTypes.number,
		noRestrict: React.PropTypes.bool,
		pressEnterToAddPhrase: React.PropTypes.string,
		noResultsPhrase: React.PropTypes.string,
		customCSSClass: React.PropTypes.string,
		hasError: React.PropTypes.bool,
		color: React.PropTypes.string,
		closeOnSelect: React.PropTypes.bool,
	},

	componentWillMount: function() {
		document.addEventListener('click', this.state.close);
	},

	componentWillUnmount: function() {
		document.removeEventListener('click', this.state.close);
	},

	getInitialState: function() {
		return {
			selectedItems: this.props.initialSelectedItems || [],
			mouseIndex: null,
			mouseActive: true,
			mouseFunc: null,
			dialogDisplayed: false,
			activeIndex: null,
			searchValue: '',
			close: (e) => {
				const eventOccurredInsideOfThisComponent = this.el ? this.el.contains(e.target) : false;
				if (!eventOccurredInsideOfThisComponent) {
					setTimeout(() => {
						if (this.state.dialogDisplayed && this.isMounted()) {
							this.props.onBlur && this.props.onBlur();
							this.setState({
								dialogDisplayed: false,
								searchValue: ''
							});
						}
					})
				}
			},
			invalid: false,
		}
	},

	componentWillReceiveProps(nextProps) {
		this.setState({
			selectedItems: nextProps.initialSelectedItems || []
		})
	},

	displayDialog: function(e) {
		if (!this.state.dialogDisplayed)
			this.props.onFocus && this.props.onFocus();
		this.setState({
			dialogDisplayed: true
		})
	},

	removeItem: function(item, e) {
		this.setState({
			selectedItems: without(this.state.selectedItems, item)
		}, this.triggerItemChange);
	},

	getItemTitle: function(item) {
		return item.label;
	},

	setActiveIndex(index) {
		this.setState({
			activeIndex: index,
			mouseIndex: null,
		}, () => {
			if (!isNull(this.state.activeIndex) && !!this.searchItems[this.state.activeIndex]) {
				this.searchItems[this.state.activeIndex].scrollIntoView();
				if (this.state.mouseActive) {
					this.setState({
						mouseActive: false,
						mouseFunc: () => {
							this.setState({
								mouseActive: true
							})
							document.removeEventListener("mousemove", this.state.mouseFunc);
						}
					}, () => {
						document.addEventListener("mousemove", this.state.mouseFunc);
					})
				}
			}
		})
	},

	handleChange: function(e) {
		e.persist();
		if (this.state.invalid && this.props.validate) {
			this.setState({
				invalid: !this.props.validate(e.target.value),
			});
		}
		this.setState({
			disabled: this.props.disableInput ? this.props.disableInput(e.target.value) : false,
		}, () => {
			this.inputChange(e.target.value);
		});
	},

	inputChange: function(newVal) {
		this.props.onInputChange && this.props.onInputChange(newVal);

		this.setState({
			searchValue: newVal
		});
	},

	keyDown: function(e) {
		const keycode = e.which;
		const activeIndex = this.state.activeIndex;
		const filterItems = this.getFilterItems(this.props.items);

		if (isNull(activeIndex) && filterItems.length !== 0) {
			this.setActiveIndex(0);
		} else if (filterItems.length === 0) {
			this.setActiveIndex(null);
		}

		if (keycode === 13) e.preventDefault();
		if(keycode === 40) { // press down key
			if (isNull(activeIndex) && filterItems.length !== 0) {
				return this.setActiveIndex(0);
			} else {
				if(activeIndex < filterItems.length - 1) {
					return this.setActiveIndex(activeIndex + 1);
				}
			}
		} else if(keycode === 38) { // press up key
			if(!activeIndex) {
				return this.setActiveIndex(0);
			} else {
				if(activeIndex > 0) {
					return this.setActiveIndex(activeIndex - 1);
				}
			}
		} else if(keycode === 13) { // press enter key
			if(!isNull(activeIndex) && filterItems.length !==0) {
				return this.selectItem(filterItems[activeIndex], e);
			} else if(this.props.noRestrict && e.currentTarget.value) {
				// if the noRestrict prop is true it adds the input as a string to the selected items on enter
				e.persist();
				return this.validateInput(e.currentTarget.value, e);
			}
		} else if(keycode === 27) { // press escape key
			return this.setState({
				activeIndex: null,
				dialogDisplayed: false
			});
		} else if(keycode === 9) { //tab key
			this.setState({
				dialogDisplayed: false,
			});
		}
	},

	triggerItemChange: function() {
		if (this.props.onChange) {
			this.props.onChange.call(null, this.state.selectedItems);
		}
	},

	getSelectedClass: function(item) {
		return includes(this.state.selectedItems, item) ? '+selected' : '';
	},

	getActiveClass: function(index) {
		return this.state.activeIndex === index || this.state.mouseIndex === index
			? styles['cpr-multi-selector-item--highlighted']
			: '';
	},

	getFilterItems: function(items = []) {
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;

		return items
			.filter((item) => {
				return getItemTitle(item).toLowerCase().indexOf(this.state.searchValue.toLowerCase()) > -1;
			})
	},

	getSearchItems: function(items = []) {
		let ItemComponent = this.props.ItemComponent || DefaultItemComponent;
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;
		let filterItems = this.getFilterItems(items);

		// Show a message that user can press enter to add new item
		if (filterItems.length === 0) {
			if (this.props.noRestrict && this.state.searchValue) {
				const pressEnterToAddPhrase = this.props.pressEnterToAddPhrase || 'Press Enter to add';
				return (
					<div className={`${styles['cpr-multi-selector-item']}`}>{pressEnterToAddPhrase} "{this.state.searchValue}"

				)
			} else {
				const noResultsPhrase = this.props.noResultsPhrase || 'No items found.';
				return (
					<div className={`${styles['cpr-multi-selector-item']}`}>{noResultsPhrase}

				)
			}
		}

		// If noRestrict & the search term doesn't have an exact match, append an additional "result" for the new item
		// This is to allow adding of new items when the search term has matching filtered items but not an exact match
		if (this.props.noRestrict && this.state.searchValue
		&& !find(filterItems, (item) => getItemTitle(item).toLowerCase() === this.state.searchValue.toLowerCase())) {
			filterItems.push(this.state.searchValue);
		}

		if (this.props.maxSearchItems) {
			filterItems.length = filterItems.length > this.props.maxSearchItems
				? this.props.maxSearchItems
				: filterItems.length;
		}

		return filterItems.map((item, index) => {
			return (

 {
						if (this.searchItems) {
							this.searchItems[index] = ref;
						} else {
							this.searchItems = [];
							this.searchItems[index] = ref;
						}
					}}
					onMouseOver={() => {
						if (this.state.mouseActive) {
							this.setState({
								mouseIndex: index
							})
						}
					}}
					className={`${styles['cpr-multi-selector-item']} ${this.getSelectedClass(item)} ${this.getActiveClass(index)}`}
					onClick={this.selectItem.bind(this, item)}>
					<ItemComponent item={item} selectedItems={this.state.selectedItems} getItemTitle={getItemTitle}/>

			)
		})
	},

	selectItem: function(item, e) {
		let selectedItems = this.state.selectedItems;

		if(includes(selectedItems, item)) {
			this.setState({
				selectedItems: without(selectedItems, item),
				invalid: false,
				dialogDisplayed: !this.props.closeOnSelect,
			}, this.triggerItemChange);
		} else {
			this.setState({
				selectedItems: union(selectedItems, [ item ]),
				invalid: false,
				dialogDisplayed: !this.props.closeOnSelect,
			}, this.triggerItemChange);
		}

		if (e && e.currentTarget) {
			e.currentTarget.value = '';
			this.inputChange('');
		}
	},

	validateInput: function(input, e) {
		if (this.state.disabled) return;
		if (this.props.validate) {
			const valid = this.props.validate(input);
			if (valid) this.selectItem(input, e);
			else this.setState({invalid: true});
		} else {
			this.selectItem(input, e);
		}
	},

	positionDialog: function() {
		setTimeout(() => {
			let el = this.el;
			let height = el.clientHeight;
			let dialog = el.querySelector(`.${styles['cpr-multi-selector__dialog']}`);

			if (dialog) {
				dialog.style.top = (height + 1) + 'px';
				el.querySelector(`.${styles['cpr-multi-selector__dialog__input']}`).focus();
			}
		}, 100);
	},

	prevent: function(e){
		if(e.which === 13) e.preventDefault();
	},

	render: function() {
		//Get getItemTitle is the function that should be passed in to decide what `pill` will display on selection.
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;

		let pills = this.state.selectedItems
			.map((item, i) => {
				return (
					<div
						key={i}
						className={`${styles['cpr-multi-selector__pill']} ${styles[`cpr-multi-selector__pill--${this.props.color || "green"}`]}`}
						title={`${getItemTitle(item)}`}>

							{getItemTitle(item)}

						<div className={`${styles['cpr-multi-selector__pill__close']}`}>
							 {
									e.stopPropagation();
									this.removeItem(item)
								}}
								className="cps-icon cps-icon-sm-neg">

				);
			});

		let dialog;

		if (this.state.dialogDisplayed) {
			let placeholder = this.props.placeholder ? this.props.placeholder : "Type a collaborators name...";
			let maxLength = this.props.maxLength;
			dialog = (
				<div className={`${styles['cpr-multi-selector__dialog']} depth-z2`} style={{}}>
					<div
						className={`${this.state.invalid ? "cps-has-error" : ""}`}
						style={{padding: "16px", borderBottom: "1px solid #E9E9E9"}}>
						<input
							onChange={this.handleChange}
							onKeyDown={this.keyDown}
							className={`cps-form-control ${styles['cpr-multi-selector__dialog__input']}`}
							placeholder={placeholder}
							{...(maxLength ? {maxLength} : {})}/>
						{this.state.invalid &&
							{this.props.invalidMsg}}

					<div
						style={this.props.dialogHeight ? {maxHeight: this.props.dialogHeight} : {}}
						className={`${styles['cpr-multi-selector__dialog__items']}`}>
						{this.getSearchItems(this.props.items)}

						{(this.state.searchValue && this.props.doneButton) &&
							<div
								style={{padding: "8px 16px", borderTop: "1px solid rgb(233, 233, 233)"}}
								className={`cps-bg-gray-3 ${styles['cpr-multi-selector__done']}`}>
								<button
									disabled={this.state.diabled}
									onClick={this.validateInput.bind(this, this.state.searchValue)}
									className={`cps-btn +primary ${this.state.disabled ? "+disabled" : ""}`}>
									DONE
								</button>

}

			)

			this.positionDialog();
		}

		return (

 { if (el) this.el = el }}
				className={`${styles['cpr-multi-selector']} ${this.state.dialogDisplayed ? styles['cpr-multi-selector--active'] : ''} ${this.props.customCSSClass || ''}`}>
				<input
					type="input" className={`${styles['cpr-multi-selector__hidden-input']} ${this.props.hasError ? styles['cpr-multi-selector__has-error'] : ''}`}
					onFocus={this.displayDialog}/>
				<div
					onClick={this.displayDialog}
					className={`${styles['cpr-multi-selector__main-input']} cps-form-control`}>
					{ pills && pills.length
						? pills
						:
{this.props.pillPlaceholder}

					}

				{dialog}

		)
	}
});

if (window && !window.MultiSelector) window.MultiSelector = MultiSelector;

export default MultiSelector;
<|endoftext|>


<commit_before>/*
 * mustache-loader.js - Mustache template loader to go with flask-mustache
 *
 * This depends on jQuery, and either:
 * - Twitter's Hogan.js:  https://github.com/twitter/hogan.js or
 * - Mustache.js:         https://github.com/janl/mustache.js
 *
 * Usage:
 *
 *   $('#target').mustache('includes/_user.mustache', {user_name:'Jan'});
 */

// authored as a jQuery plugin
(function($) {

    // this is a cached lookup table of templates
    var cache = {};

    var load = function(templateName) {
        // this function takes names like: "includes/_user.mustache"
        // and loads them from somewhere else.

        // they can be cached as functions, or as strings.
        // Strings are template content.
        if (typeof cache[templateName] === 'undefined') {
            // first we need to convert slashes to hyphens, since
            // they're DOM valid
            var domTemplateName = templateName.replace('/', '-');

            if (document.getElementById(domTemplateName)) {
                // stupid hack to turn HTML-encoded templates into strings, see:
                // http://stackoverflow.com/a/2419664/61435
                cache[templateName] = $('<div />').html(
                    $(document.getElementById(domTemplateName)).html()).text();
            }
        }

        return cache[templateName];
    };

    var render = function(templateName, context) {

        // first we need to try and load the template
        var template = load(templateName);

        if (typeof template === 'undefined') {
            $.error('Unknown template ' + templateName);
        }

        else if (typeof template === 'function') {
            // template has been pre-compiled, just return it
            return template(context);
        }

        // template hasn't been pre-compiled yet
        // so we need to do other things
        if (window.Hogan) {
            return Hogan.compile(template).render(context);
        }
        else if (window.Mustache) {
            return Mustache.render(template, context);
        }

        // we don't have Hogan or Mustache, so we need to bail
        $.error('Must have either Hogan.js or Mustache.js to load string templates');
    };

    $.fn.mustache = function(templateName, context) {
        // replaces the content of the passed in element with the content
        // rendered by Mustache

        return this.html(render(templateName, context));
    };

})(jQuery);<commit_msg>Trim in-HTML templates<commit_after>/*
 * mustache-loader.js - Mustache template loader to go with flask-mustache
 *
 * This depends on jQuery, and either:
 * - Twitter's Hogan.js:  https://github.com/twitter/hogan.js or
 * - Mustache.js:         https://github.com/janl/mustache.js
 *
 * Usage:
 *
 *   $('#target').mustache('includes/_user.mustache', {user_name:'Jan'});
 */

// authored as a jQuery plugin
(function($) {

    // this is a cached lookup table of templates
    var cache = {};

    var load = function(templateName) {
        // this function takes names like: "includes/_user.mustache"
        // and loads them from somewhere else.

        // they can be cached as functions, or as strings.
        // Strings are template content.
        if (typeof cache[templateName] === 'undefined') {
            // first we need to convert slashes to hyphens, since
            // they're DOM valid
            var domTemplateName = templateName.replace('/', '-');

            if (document.getElementById(domTemplateName)) {
                // stupid hack to turn HTML-encoded templates into strings, see:
                // http://stackoverflow.com/a/2419664/61435
                cache[templateName] = $('<div />').html(
                    $(document.getElementById(domTemplateName)).html().trim()).text();
            }
        }

        return cache[templateName];
    };

    var render = function(templateName, context) {

        // first we need to try and load the template
        var template = load(templateName);

        if (typeof template === 'undefined') {
            $.error('Unknown template ' + templateName);
        }

        else if (typeof template === 'function') {
            // template has been pre-compiled, just return it
            return template(context);
        }

        // template hasn't been pre-compiled yet
        // so we need to do other things
        if (window.Hogan) {
            return Hogan.compile(template).render(context);
        }
        else if (window.Mustache) {
            return Mustache.render(template, context);
        }

        // we don't have Hogan or Mustache, so we need to bail
        $.error('Must have either Hogan.js or Mustache.js to load string templates');
    };

    $.fn.mustache = function(templateName, context) {
        // replaces the content of the passed in element with the content
        // rendered by Mustache

        return this.html(render(templateName, context));
    };

})(jQuery);<|endoftext|>