<commit_before>/*
 * prompt.js: Simple prompt for prompting information from the command line
 *
 * (C) 2010, Nodejitsu Inc.
 *
 */

var events = require('events'),
    async = require('async'),
    colors = require('colors'),
    winston = require('winston'),
    stdio = process.binding('stdio');

//
// ### @private function capitalize (str)
// #### str {string} String to capitalize
// Capitalizes the string supplied.
//
function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

//
// Expose version using `pkginfo`
//
require('pkginfo')(module, 'version');

var stdin, stdout, history = [];
var prompt = module.exports = Object.create(events.EventEmitter.prototype);
var logger = prompt.logger = new winston.Logger({
  transports: [new (winston.transports.Console)()]
});

prompt.started    = false;
prompt.paused     = false;
prompt.allowEmpty = false;
prompt.message    = 'prompt';
prompt.delimiter  = ':';

//
// Create an empty object for the properties
// known to `node-prompt`
//
prompt.properties = {};

//
// Setup the default winston logger to use
// the `cli` levels and colors.
//
logger.cli();

//
// ### function start (options)
// #### @options {Object} **Optional** Options to consume by prompt
// Starts the prompt by listening to the appropriate events on `options.stdin`
// and `options.stdout`. If no streams are supplied, then `process.stdin`
// and `process.stdout` are used, respectively.
//
prompt.start = function (options) {
  if (prompt.started) {
    return;
  }

  options = options        || {};
  stdin   = options.stdin  || process.openStdin();
  stdout  = options.stdout || process.stdout;

  //
  // By default: Remeber the last `10` prompt property /
  // answer pairs and don't allow empty responses globally.
  //
  prompt.memory     = options.memory     || 10;
  prompt.allowEmpty = options.allowEmpty || false;
  prompt.message    = options.message    || prompt.message;
  prompt.delimiter  = options.delimiter  || prompt.delimiter;

  process.on('SIGINT', function () {
    stdout.write('\n');
    process.exit(1);
  });

  prompt.emit('start');
  prompt.started = true;
  return prompt;
};

//
// ### function pause ()
// Pauses input coming in from stdin
//
prompt.pause = function () {
  if (!prompt.started || prompt.paused) {
    return;
  }

  stdin.pause();
  prompt.emit('pause');
  prompt.paused = true;
  return prompt;
};

//
// ### function resume ()
// Resumes input coming in from stdin
//
prompt.resume = function () {
  if (!prompt.started || !prompt.paused) {
    return;
  }

  stdin.resume();
  prompt.emit('resume');
  prompt.paused = false;
  return prompt;
};

//
// ### function history (search)
// #### @search {Number|string} Index or property name to find.
// Returns the `property:value` pair from within the prompts
// `history` array.
//
prompt.history = function (search) {
  if (typeof search === 'number') {
    return history[search] || {};
  }

  var names = history.map(function (pair) {
    return typeof pair.property === 'string'
      ? pair.property
      : pair.property.name;
  });

  if (~names.indexOf(search)) {
    return null;
  }

  return history.filter(function (name) {
    return typeof pair.property === 'string'
      ? pair.property === name
      : pair.property.name === name;
  })[0];
};

//
// ### function get (msg, [validator,] callback)
// #### @msg {Array|Object|string} Set of variables to get input for.
// #### @callback {function} Continuation to pass control to when complete.
// Gets input from the user via stdin for the specified message(s) `msg`.
//
prompt.get = function (msg, callback) {
  var vars = !Array.isArray(msg) ? [msg] : msg,
      result = {};

  vars = vars.map(function (v) {
    if (typeof v === 'string') {
      v = v.toLowerCase();
    }

    return prompt.properties[v] || v;
  });

  function get(target, next) {
    prompt.getInput(target, function (err, line) {
      if (err) {
        return next(err);
      }

      var name = target.name || target;
      result[name] = line;
      next();
    });
  }

  async.forEachSeries(vars, get, function (err) {
    return err ? callback(err) : callback(null, result);
  });

  return prompt;
};

//
// ### function getInput (msg, validator, callback)
// #### @msg {Object|string} Variable to get input for.
// #### @callback {function} Continuation to pass control to when complete.
// Gets input from the user via stdin for the specified message `msg`.
//
prompt.getInput = function (prop, callback) {
  var name   = prop.message || prop.name || prop,
      propName = prop.name || prop,
      delim  = prompt.delimiter + ' ',
      raw    = [prompt.message, delim + name.grey, delim.grey],
      read   = prop.hidden ? prompt.readLineHidden : prompt.readLine,
      length, msg;

  if (prompt.override && prompt.override [propName]) {
    return callback (null, prompt.override [propName])
  }

  if (prop.default) {
    raw.splice(2, -1, ' (' + prop.default + ')');
  }

  // Calculate the raw length and colorize the prompt
  length = raw.join('').length;
  raw[0] = raw[0];
  msg = raw.join('');

  if (prop.help) {
    prop.help.forEach(function (line) {
      logger.help(line);
    });
  }

  stdout.write(msg);
  prompt.emit('prompt', prop);

  read.call(null, function (err, line) {
    if (err) {
      return callback(err);
    }

    if (!line || line === '') {
      line = prop.default || line;
    }

    if (!prop.validator && prop.empty !== false) {
      logger.input(line.yellow);
      return callback(null, line);
    }

    var valid = true,
        validator = prop.validator;

    function next(valid) {
      if (arguments.length < 1) {
        valid = true;
      }

      if (prop.empty === false && valid) {
        valid = line.length > 0;
        if (!valid) {
          prop.warning = prop.warning || 'You must supply a value.';
        }
      }

      if (!valid) {
        logger.error('Invalid input for ' + name.grey);
        if (prop.warning) {
          logger.error(prop.warning);
        }

        prompt.emit('invalid', prop, line);
        return prompt.getInput(prop, callback);
      }

      //
      // Log the resulting line, append this `property:value`
      // pair to the history for `node-prompt` and respond to
      // the callback.
      //
      logger.input(line.yellow);
      prompt._remember(prop, line);
      callback(null, line);
    }

    if (validator) {
      if (validator.test) {
        valid = validator.test(line)
      }
      else if (typeof validator === 'function') {
        return validator.length < 2
          ? next(validator(line))
          : validator(line, next);
      }
      else {
        return callback(new Error('Invalid valiator: ' + typeof validator));
      }
    }

    next(valid);
  });

  return prompt;
};

//
// ### function addProperties (obj, properties, callback)
// #### @obj {Object} Object to add properties to
// #### @properties {Array} List of properties to get values for
// #### @callback {function} Continuation to pass control to when complete.
// Prompts the user for values each of the `properties` if `obj` does not already
// have a value for the property. Responds with the modified object.
//
prompt.addProperties = function (obj, properties, callback) {
  properties = properties.filter(function (prop) {
    return typeof obj[prop] === 'undefined';
  });

  if (properties.length === 0) {
    return callback(obj);
  }

  prompt.get(properties, function (err, results) {
    if (err) {
      return callback(err);
    }
    else if (!results) {
      return callback(null, obj);
    }

    function putNested (obj, path, value) {
      var last = obj, key;

      while (path.length > 1) {
        key = path.shift();
        if (!last[key]) {
          last[key] = {};
        }

        last = last[key];
      }

      last[path.shift()] = value;
    }

    Object.keys(results).forEach(function (key) {
      putNested(obj, key.split('.'), results[key]);
    });

    callback(null, obj);
  });

  return prompt;
};

//
// ### function readLine (callback)
// #### @callback {function} Continuation to respond to when complete
// Gets a single line of input from the user.
//
prompt.readLine = function (callback) {
  var value = '', buffer = '';
  prompt.resume();
  stdin.setEncoding('utf8');
  stdin.on('error', callback);
  stdin.on('data', function data (chunk) {
    value += buffer + chunk;
    buffer = '';
    value = value.replace(/\r/g, '');
    if (value.indexOf('\n') !== -1) {
      if (value !== '\n') {
        value = value.replace(/^\n+/, '');
      }

      buffer = value.substr(value.indexOf('\n'));
      value = value.substr(0, value.indexOf('\n'));
      prompt.pause();
      stdin.removeListener('data', data);
      stdin.removeListener('error', callback);
      value = value.trim();
      callback(null, value);
    }
  });

  return prompt;
};

//
// ### function readLineHidden (callback)
// #### @callback {function} Continuation to respond to when complete
// Gets a single line of hidden input (i.e. `rawMode = true`) from the user.
//
prompt.readLineHidden = function (callback) {
  var value = '';

  //
  // Ignore errors from `.setRawMode()` so that `node-prompt` can
  // be scripted in child processes.
  //
  try { stdio.setRawMode(true) }
  catch (ex) { }

  prompt.resume();
  stdin.on('error', callback);
  stdin.on('data', function data (line) {
    line = line + '';
    for(var i = 0; i < line.length; i++) {
      c = line[i];
      switch (c) {
        case '\n': case '\r': case '\r\n': case '\u0004':
          stdio.setRawMode(false);
          stdin.removeListener('data', data);
          stdin.removeListener('error', callback);
          value = value.trim();
          stdout.write('\n');
          stdout.flush && stdout.flush();
          prompt.pause();
          return callback(null, value);
        case '\x7f': case'\x08':
          value = value.slice(0,-1);
          break;
        case '\u0003': case '\0':
          stdout.write('\n');
          process.exit(1);
          break;
        default:
          value = value + c;
          break;
      }
    }
  });

  return prompt;
};

//
// ### @private function _remember (property, value)
// #### @property {Object|string} Property that the value is in response to.
// #### @value {string} User input captured by `node-prompt`.
// Prepends the `property:value` pair into the private `history` Array
// for `node-prompt` so that it can be accessed later.
//
prompt._remember = function (property, value) {
  history.unshift({
    property: property,
    value: value
  });

  //
  // If the length of the `history` Array
  // has exceeded the specified length to remember,
  // `prompt.memory`, truncate it.
  //
  if (history.length > prompt.memory) {
    history.splice(prompt.memory, history.length - prompt.memory);
  }
};
<commit_msg>[v0.6] Use `tty` instead of `process.binding('stdio')`<commit_after>/*
 * prompt.js: Simple prompt for prompting information from the command line
 *
 * (C) 2010, Nodejitsu Inc.
 *
 */

var events = require('events'),
    async = require('async'),
    colors = require('colors'),
    winston = require('winston'),
    tty = require('tty');

//
// ### @private function capitalize (str)
// #### str {string} String to capitalize
// Capitalizes the string supplied.
//
function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

//
// Expose version using `pkginfo`
//
require('pkginfo')(module, 'version');

var stdin, stdout, history = [];
var prompt = module.exports = Object.create(events.EventEmitter.prototype);
var logger = prompt.logger = new winston.Logger({
  transports: [new (winston.transports.Console)()]
});

prompt.started    = false;
prompt.paused     = false;
prompt.allowEmpty = false;
prompt.message    = 'prompt';
prompt.delimiter  = ':';

//
// Create an empty object for the properties
// known to `node-prompt`
//
prompt.properties = {};

//
// Setup the default winston logger to use
// the `cli` levels and colors.
//
logger.cli();

//
// ### function start (options)
// #### @options {Object} **Optional** Options to consume by prompt
// Starts the prompt by listening to the appropriate events on `options.stdin`
// and `options.stdout`. If no streams are supplied, then `process.stdin`
// and `process.stdout` are used, respectively.
//
prompt.start = function (options) {
  if (prompt.started) {
    return;
  }

  options = options        || {};
  stdin   = options.stdin  || process.openStdin();
  stdout  = options.stdout || process.stdout;

  //
  // By default: Remeber the last `10` prompt property /
  // answer pairs and don't allow empty responses globally.
  //
  prompt.memory     = options.memory     || 10;
  prompt.allowEmpty = options.allowEmpty || false;
  prompt.message    = options.message    || prompt.message;
  prompt.delimiter  = options.delimiter  || prompt.delimiter;

  process.on('SIGINT', function () {
    stdout.write('\n');
    process.exit(1);
  });

  prompt.emit('start');
  prompt.started = true;
  return prompt;
};

//
// ### function pause ()
// Pauses input coming in from stdin
//
prompt.pause = function () {
  if (!prompt.started || prompt.paused) {
    return;
  }

  stdin.pause();
  prompt.emit('pause');
  prompt.paused = true;
  return prompt;
};

//
// ### function resume ()
// Resumes input coming in from stdin
//
prompt.resume = function () {
  if (!prompt.started || !prompt.paused) {
    return;
  }

  stdin.resume();
  prompt.emit('resume');
  prompt.paused = false;
  return prompt;
};

//
// ### function history (search)
// #### @search {Number|string} Index or property name to find.
// Returns the `property:value` pair from within the prompts
// `history` array.
//
prompt.history = function (search) {
  if (typeof search === 'number') {
    return history[search] || {};
  }

  var names = history.map(function (pair) {
    return typeof pair.property === 'string'
      ? pair.property
      : pair.property.name;
  });

  if (~names.indexOf(search)) {
    return null;
  }

  return history.filter(function (name) {
    return typeof pair.property === 'string'
      ? pair.property === name
      : pair.property.name === name;
  })[0];
};

//
// ### function get (msg, [validator,] callback)
// #### @msg {Array|Object|string} Set of variables to get input for.
// #### @callback {function} Continuation to pass control to when complete.
// Gets input from the user via stdin for the specified message(s) `msg`.
//
prompt.get = function (msg, callback) {
  var vars = !Array.isArray(msg) ? [msg] : msg,
      result = {};

  vars = vars.map(function (v) {
    if (typeof v === 'string') {
      v = v.toLowerCase();
    }

    return prompt.properties[v] || v;
  });

  function get(target, next) {
    prompt.getInput(target, function (err, line) {
      if (err) {
        return next(err);
      }

      var name = target.name || target;
      result[name] = line;
      next();
    });
  }

  async.forEachSeries(vars, get, function (err) {
    return err ? callback(err) : callback(null, result);
  });

  return prompt;
};

//
// ### function getInput (msg, validator, callback)
// #### @msg {Object|string} Variable to get input for.
// #### @callback {function} Continuation to pass control to when complete.
// Gets input from the user via stdin for the specified message `msg`.
//
prompt.getInput = function (prop, callback) {
  var name   = prop.message || prop.name || prop,
      propName = prop.name || prop,
      delim  = prompt.delimiter + ' ',
      raw    = [prompt.message, delim + name.grey, delim.grey],
      read   = prop.hidden ? prompt.readLineHidden : prompt.readLine,
      length, msg;

  if (prompt.override && prompt.override [propName]) {
    return callback (null, prompt.override [propName])
  }

  if (prop.default) {
    raw.splice(2, -1, ' (' + prop.default + ')');
  }

  // Calculate the raw length and colorize the prompt
  length = raw.join('').length;
  raw[0] = raw[0];
  msg = raw.join('');

  if (prop.help) {
    prop.help.forEach(function (line) {
      logger.help(line);
    });
  }

  stdout.write(msg);
  prompt.emit('prompt', prop);

  read.call(null, function (err, line) {
    if (err) {
      return callback(err);
    }

    if (!line || line === '') {
      line = prop.default || line;
    }

    if (!prop.validator && prop.empty !== false) {
      logger.input(line.yellow);
      return callback(null, line);
    }

    var valid = true,
        validator = prop.validator;

    function next(valid) {
      if (arguments.length < 1) {
        valid = true;
      }

      if (prop.empty === false && valid) {
        valid = line.length > 0;
        if (!valid) {
          prop.warning = prop.warning || 'You must supply a value.';
        }
      }

      if (!valid) {
        logger.error('Invalid input for ' + name.grey);
        if (prop.warning) {
          logger.error(prop.warning);
        }

        prompt.emit('invalid', prop, line);
        return prompt.getInput(prop, callback);
      }

      //
      // Log the resulting line, append this `property:value`
      // pair to the history for `node-prompt` and respond to
      // the callback.
      //
      logger.input(line.yellow);
      prompt._remember(prop, line);
      callback(null, line);
    }

    if (validator) {
      if (validator.test) {
        valid = validator.test(line)
      }
      else if (typeof validator === 'function') {
        return validator.length < 2
          ? next(validator(line))
          : validator(line, next);
      }
      else {
        return callback(new Error('Invalid valiator: ' + typeof validator));
      }
    }

    next(valid);
  });

  return prompt;
};

//
// ### function addProperties (obj, properties, callback)
// #### @obj {Object} Object to add properties to
// #### @properties {Array} List of properties to get values for
// #### @callback {function} Continuation to pass control to when complete.
// Prompts the user for values each of the `properties` if `obj` does not already
// have a value for the property. Responds with the modified object.
//
prompt.addProperties = function (obj, properties, callback) {
  properties = properties.filter(function (prop) {
    return typeof obj[prop] === 'undefined';
  });

  if (properties.length === 0) {
    return callback(obj);
  }

  prompt.get(properties, function (err, results) {
    if (err) {
      return callback(err);
    }
    else if (!results) {
      return callback(null, obj);
    }

    function putNested (obj, path, value) {
      var last = obj, key;

      while (path.length > 1) {
        key = path.shift();
        if (!last[key]) {
          last[key] = {};
        }

        last = last[key];
      }

      last[path.shift()] = value;
    }

    Object.keys(results).forEach(function (key) {
      putNested(obj, key.split('.'), results[key]);
    });

    callback(null, obj);
  });

  return prompt;
};

//
// ### function readLine (callback)
// #### @callback {function} Continuation to respond to when complete
// Gets a single line of input from the user.
//
prompt.readLine = function (callback) {
  var value = '', buffer = '';
  prompt.resume();
  stdin.setEncoding('utf8');
  stdin.on('error', callback);
  stdin.on('data', function data (chunk) {
    value += buffer + chunk;
    buffer = '';
    value = value.replace(/\r/g, '');
    if (value.indexOf('\n') !== -1) {
      if (value !== '\n') {
        value = value.replace(/^\n+/, '');
      }

      buffer = value.substr(value.indexOf('\n'));
      value = value.substr(0, value.indexOf('\n'));
      prompt.pause();
      stdin.removeListener('data', data);
      stdin.removeListener('error', callback);
      value = value.trim();
      callback(null, value);
    }
  });

  return prompt;
};

//
// ### function readLineHidden (callback)
// #### @callback {function} Continuation to respond to when complete
// Gets a single line of hidden input (i.e. `rawMode = true`) from the user.
//
prompt.readLineHidden = function (callback) {
  var value = '';

  //
  // Ignore errors from `.setRawMode()` so that `node-prompt` can
  // be scripted in child processes.
  //
  try { tty.setRawMode(true) }
  catch (ex) { }

  prompt.resume();
  stdin.on('error', callback);
  stdin.on('data', function data (line) {
    line = line + '';
    for(var i = 0; i < line.length; i++) {
      c = line[i];
      switch (c) {
        case '\n': case '\r': case '\r\n': case '\u0004':
          tty.setRawMode(false);
          stdin.removeListener('data', data);
          stdin.removeListener('error', callback);
          value = value.trim();
          stdout.write('\n');
          stdout.flush && stdout.flush();
          prompt.pause();
          return callback(null, value);
        case '\x7f': case'\x08':
          value = value.slice(0,-1);
          break;
        case '\u0003': case '\0':
          stdout.write('\n');
          process.exit(1);
          break;
        default:
          value = value + c;
          break;
      }
    }
  });

  return prompt;
};

//
// ### @private function _remember (property, value)
// #### @property {Object|string} Property that the value is in response to.
// #### @value {string} User input captured by `node-prompt`.
// Prepends the `property:value` pair into the private `history` Array
// for `node-prompt` so that it can be accessed later.
//
prompt._remember = function (property, value) {
  history.unshift({
    property: property,
    value: value
  });

  //
  // If the length of the `history` Array
  // has exceeded the specified length to remember,
  // `prompt.memory`, truncate it.
  //
  if (history.length > prompt.memory) {
    history.splice(prompt.memory, history.length - prompt.memory);
  }
};
<|endoftext|>


<commit_before>//     (c) 2010 Michael Aufreiter
//     Proper is freely distributable under the MIT license.
//     For all details and documentation:
//     http://github.com/michael/proper

(function(){

  // _.Events (borrowed from Backbone.js)
  // -----------------

  // A module that can be mixed in to *any object* in order to provide it with
  // custom events. You may `bind` or `unbind` a callback function to an event;
  // `trigger`-ing an event fires all callbacks in succession.
  //
  //     var object = {};
  //     _.extend(object, Backbone.Events);
  //     object.bind('expand', function(){ alert('expanded'); });
  //     object.trigger('expand');
  //

  _.Events = window.Backbone ? Backbone.Events : {

    // Bind an event, specified by a string name, `ev`, to a `callback` function.
    // Passing `"all"` will bind the callback to all events fired.
    bind : function(ev, callback) {
      var calls = this._callbacks || (this._callbacks = {});
      var list  = this._callbacks[ev] || (this._callbacks[ev] = []);
      list.push(callback);
      return this;
    },

    // Remove one or many callbacks. If `callback` is null, removes all
    // callbacks for the event. If `ev` is null, removes all bound callbacks
    // for all events.
    unbind : function(ev, callback) {
      var calls;
      if (!ev) {
        this._callbacks = {};
      } else if (calls = this._callbacks) {
        if (!callback) {
          calls[ev] = [];
        } else {
          var list = calls[ev];
          if (!list) return this;
          for (var i = 0, l = list.length; i < l; i++) {
            if (callback === list[i]) {
              list.splice(i, 1);
              break;
            }
          }
        }
      }
      return this;
    },

    // Trigger an event, firing all bound callbacks. Callbacks are passed the
    // same arguments as `trigger` is, apart from the event name.
    // Listening for `"all"` passes the true event name as the first argument.
    trigger : function(ev) {
      var list, calls, i, l;
      if (!(calls = this._callbacks)) return this;
      if (list = calls[ev]) {
        for (i = 0, l = list.length; i < l; i++) {
          list[i].apply(this, Array.prototype.slice.call(arguments, 1));
        }
      }
      if (list = calls['all']) {
        for (i = 0, l = list.length; i < l; i++) {
          list[i].apply(this, arguments);
        }
      }
      return this;
    }
  };

  Sanitize.Config = {}
  Sanitize.Config.BASIC = {
    elements: [
       'a', 'b', 'br', 'em', 'i', 'li', 'ol', 'p', 'strong', 'code', 'ul'],

     attributes: {
       'a': ['href'],
     },

     protocols: {
       'a': {'href': ['ftp', 'http', 'https', 'mailto', Sanitize.RELATIVE]}
     }
  };

  // Initial Setup
  // -------------

  controlsTpl = ' \

 \

Emphasis
 \

Strong
 \

Code
 \

|
 \

Bullets List
 \

Numbered List
 \

Indent
 \

Outdent
 \

|
 \

Link
 \

 \

 \
  ';

  // Proper
  // -----------

  this.Proper = function(options) {
    var activeElement = null,     // element that is being edited
        $controls,
        self = {},
        that = this,
        pendingChange = false;

    // Setup temporary hidden DOM Node, for sanitization
    $('body').append($('

').hide());

    // Commands
    // -----------

    var commands = {
      execEM: function() {
        document.execCommand('italic', false, true);
        return false;
      },

      execSTRONG: function() {
        document.execCommand('bold', false, true);
        return false;
      },

      execCODE: function() {
        document.execCommand('insertHTML', false, ''+window.getSelection()+'');
        return false;
      },

      execUL: function() {
        document.execCommand('insertUnorderedList', false, null);
        return false;
      },

      execOL: function() {
        document.execCommand('insertOrderedList', false, null);
        return false;
      },

      execINDENT: function() {
        document.execCommand('indent', false, null);
        return false;
      },

      execOUTDENT: function() {
        document.execCommand('outdent', false, null);
        return false;
      },

      execLINK: function() {
        document.execCommand('createLink', false, prompt('URL:'));
        return false;
      },

      showHTML: function() {
        alert($(this.el).html());
      }
    };

    // Clean up the mess produced by contenteditable
    function sanitize(element) {
      var s = new Sanitize(Sanitize.Config.BASIC);
      var content = s.clean_node(element);
      $('#proper_content').html(content);
    }

    function semantify(element) {
      $(element).find('b').each(function() {
        $(this).replaceWith($('').html($(this).html()));
      });

      $(element).find('i').each(function() {
        $(this).replaceWith($('').html($(this).html()));
      });
    }

    function bindEvents(el) {
      $(el).bind('paste', function() {
        // Immediately sanitize pasted content
        setTimeout(function() {
          sanitize($(el)[0]);
          $(el).html($('#proper_content').html());
        }, 10);
      });

      $(el).bind('keyup', function() {
        // Trigger change events, but consolidate them to 200ms time slices
        setTimeout(function() {
          // Skip if there's already a change pending
          if (!pendingChange) {
            pendingChange = true;
            setTimeout(function() {
              pendingChange = false;

              // Sanitize on every keystroke
              sanitize($(activeElement)[0]);
              semantify($('#proper_content')[0]);

              self.trigger('changed');
            }, 200);
          }
        }, 10);

        return true;
      });
    }

    // Instance methods
    // -----------

    self.deactivate = function() {
      $(activeElement).attr('contenteditable', 'false');
      $(activeElement).unbind('paste');
      $controls.hide();
      self.unbind('changed');
    };

    // Activate editor for a given element
    self.activate = function(el) {

      if (el !== activeElement) {
        // Deactivate previously active element
        self.deactivate();

        // Make editable
        $(el).attr('contenteditable', true);
        activeElement = el;
        bindEvents(el);

        // Show and reposition controls

        // Init sanitized content
        $('#proper_content').html($(el).html());

        $controls.insertBefore(el);
        $controls.show();
      }
    };

    // Get current content
    self.content = function() {
      return activeElement ? $('#proper_content').html() : '';
    };

    $controls = $(controlsTpl); // the controls panel
    $controls.prependTo($('body')).hide();

    // Bind events for controls
    $controls.find('.proper-commands a.command').click(function(e) {
      commands['exec'+ $(e.currentTarget).attr('command').toUpperCase()]();
      setTimeout(function() {
        sanitize($(activeElement)[0]);
        semantify($('#proper_content')[0]);
        self.trigger('changed');
      }, 10);

      return false;
    });

    // Expose public API
    // -----------

    _.extend(self, _.Events);
    return self;
  };

})();<commit_msg>There was still a problem with the editor commands.<commit_after>//     (c) 2010 Michael Aufreiter
//     Proper is freely distributable under the MIT license.
//     For all details and documentation:
//     http://github.com/michael/proper

(function(){

  // _.Events (borrowed from Backbone.js)
  // -----------------

  // A module that can be mixed in to *any object* in order to provide it with
  // custom events. You may `bind` or `unbind` a callback function to an event;
  // `trigger`-ing an event fires all callbacks in succession.
  //
  //     var object = {};
  //     _.extend(object, Backbone.Events);
  //     object.bind('expand', function(){ alert('expanded'); });
  //     object.trigger('expand');
  //

  _.Events = window.Backbone ? Backbone.Events : {

    // Bind an event, specified by a string name, `ev`, to a `callback` function.
    // Passing `"all"` will bind the callback to all events fired.
    bind : function(ev, callback) {
      var calls = this._callbacks || (this._callbacks = {});
      var list  = this._callbacks[ev] || (this._callbacks[ev] = []);
      list.push(callback);
      return this;
    },

    // Remove one or many callbacks. If `callback` is null, removes all
    // callbacks for the event. If `ev` is null, removes all bound callbacks
    // for all events.
    unbind : function(ev, callback) {
      var calls;
      if (!ev) {
        this._callbacks = {};
      } else if (calls = this._callbacks) {
        if (!callback) {
          calls[ev] = [];
        } else {
          var list = calls[ev];
          if (!list) return this;
          for (var i = 0, l = list.length; i < l; i++) {
            if (callback === list[i]) {
              list.splice(i, 1);
              break;
            }
          }
        }
      }
      return this;
    },

    // Trigger an event, firing all bound callbacks. Callbacks are passed the
    // same arguments as `trigger` is, apart from the event name.
    // Listening for `"all"` passes the true event name as the first argument.
    trigger : function(ev) {
      var list, calls, i, l;
      if (!(calls = this._callbacks)) return this;
      if (list = calls[ev]) {
        for (i = 0, l = list.length; i < l; i++) {
          list[i].apply(this, Array.prototype.slice.call(arguments, 1));
        }
      }
      if (list = calls['all']) {
        for (i = 0, l = list.length; i < l; i++) {
          list[i].apply(this, arguments);
        }
      }
      return this;
    }
  };

  Sanitize.Config = {}
  Sanitize.Config.BASIC = {
    elements: [
       'a', 'b', 'br', 'em', 'i', 'li', 'ol', 'p', 'strong', 'code', 'ul'],

     attributes: {
       'a': ['href'],
     },

     protocols: {
       'a': {'href': ['ftp', 'http', 'https', 'mailto', Sanitize.RELATIVE]}
     }
  };

  // Initial Setup
  // -------------

  controlsTpl = ' \

 \

Emphasis
 \

Strong
 \

Code
 \

|
 \

Bullets List
 \

Numbered List
 \

Indent
 \

Outdent
 \

|
 \

Link
 \

 \

 \
  ';

  // Proper
  // -----------

  this.Proper = function(options) {
    var activeElement = null,     // element that is being edited
        $controls,
        self = {},
        that = this,
        pendingChange = false;

    // Setup temporary hidden DOM Node, for sanitization
    $('body').append($('

').hide());

    // Commands
    // -----------

    var commands = {
      execEM: function() {
        document.execCommand('italic', false, true);
        return false;
      },

      execSTRONG: function() {
        document.execCommand('bold', false, true);
        return false;
      },

      execCODE: function() {
        document.execCommand('insertHTML', false, ''+window.getSelection()+'');
        return false;
      },

      execUL: function() {
        document.execCommand('insertUnorderedList', false, null);
        return false;
      },

      execOL: function() {
        document.execCommand('insertOrderedList', false, null);
        return false;
      },

      execINDENT: function() {
        document.execCommand('indent', false, null);
        return false;
      },

      execOUTDENT: function() {
        document.execCommand('outdent', false, null);
        return false;
      },

      execLINK: function() {
        document.execCommand('createLink', false, prompt('URL:'));
        return false;
      },

      showHTML: function() {
        alert($(this.el).html());
      }
    };

    // Clean up the mess produced by contenteditable
    function sanitize(element) {
      var s = new Sanitize(Sanitize.Config.BASIC);
      var content = s.clean_node(element);
      $('#proper_content').html(content);
    }

    function semantify(element) {
      $(element).find('b').each(function() {
        $(this).replaceWith($('').html($(this).html()));
      });

      $(element).find('i').each(function() {
        $(this).replaceWith($('').html($(this).html()));
      });
    }

    function bindEvents(el) {
      $(el).bind('paste', function() {
        // Immediately sanitize pasted content
        setTimeout(function() {
          sanitize($(el)[0]);
          $(el).html($('#proper_content').html());
        }, 10);
      });

      $(el).bind('keyup', function() {
        // Trigger change events, but consolidate them to 200ms time slices
        setTimeout(function() {
          // Skip if there's already a change pending
          if (!pendingChange) {
            pendingChange = true;
            setTimeout(function() {
              pendingChange = false;

              // Sanitize on every registered change
              sanitize($(activeElement)[0]);
              semantify($('#proper_content')[0]);
              self.trigger('changed');
            }, 200);
          }
        }, 10);

        return true;
      });
    }

    // Instance methods
    // -----------

    self.deactivate = function() {
      $(activeElement).attr('contenteditable', 'false');
      $(activeElement).unbind('paste');
      $('.proper-commands').remove();
      self.unbind('changed');
    };

    // Activate editor for a given element
    self.activate = function(el) {

      if (el !== activeElement) {
        // Deactivate previously active element
        self.deactivate();

        // Make editable
        $(el).attr('contenteditable', true);
        activeElement = el;
        bindEvents(el);

        // Init sanitized content
        $('#proper_content').html($(el).html());

        // Setup controls
        $controls = $(controlsTpl);
        $controls.insertBefore(el);
        $('.proper-commands a.command').click(function(e) {
          commands['exec'+ $(e.currentTarget).attr('command').toUpperCase()]();
          setTimeout(function() {
            sanitize($(activeElement)[0]);
            semantify($('#proper_content')[0]);
            self.trigger('changed');
          }, 10);

          return false;
        });
      }
    };

    // Get current content
    self.content = function() {
      return activeElement ? $('#proper_content').html() : '';
    };

    // Expose public API
    // -----------

    _.extend(self, _.Events);
    return self;
  };

})();<|endoftext|>


<commit_before>/*jshint unused:vars */

/*
 * raygun
 * https://github.com/MindscapeHQ/raygun4node
 *
 * Copyright (c) 2015 MindscapeHQ
 * Licensed under the MIT license.
 */

'use strict';

var raygunTransport = require('./raygun.transport');
var MessageBuilder = require('./raygun.messageBuilder');
var offlineStorage = require('./raygun.offline');

var Raygun = function () {
  var _apiKey, _filters, raygun = this, _user, _version, _host, _port, _useSSL, _onBeforeSend, _offlineStorage, _isOnline, _cachePath;

  raygun.init = function (options) {
    _apiKey = options.apiKey;
    _filters = options.filters;
    _host = options.host;
    _port = options.port;
    _useSSL = options.useSSL || true;
    _onBeforeSend = options.onBeforeSend;
    _offlineStorage = options.offlineStorage === undefined ? new offlineStorage() : options.offlineStorage;
    _isOnline = options.isOnline;
    _cachePath = options.cachePath;

    if(!_isOnline) {
      if(!_cachePath) {
        throw new Error("Cache Path must be defined before Raygun can cache offline");
      }

      _offlineStorage.init(_cachePath);
    }

    return raygun;
  };

  raygun.user = function (req) {
    return;
  };

  // This function is deprecated, is provided for legacy apps and will be
  // removed in 1.0: use raygun.user instead
  raygun.setUser = function (user) {
    _user = user;
    return raygun;
  };

  raygun.setVersion = function (version) {
    _version = version;
    return raygun;
  };

  raygun.onBeforeSend = function (onBeforeSend) {
    _onBeforeSend = onBeforeSend;
    return raygun;
  };

  raygun.offline = function() {
      if(!_cachePath) {
        throw new Error("Cache Path must be defined before Raygun can cache offline");
      }

      _offlineStorage.init(_cachePath);
      _isOnline = false;
  };

  raygun.online = function(callback) {
    _isOnline = true;
    _offlineStorage.send(callback);
  };

  raygun.send = function (exception, customData, callback, request, tags) {
    var builder = new MessageBuilder({ filters: _filters })
      .setErrorDetails(exception)
      .setRequestDetails(request)
      .setMachineName()
      .setEnvironmentDetails()
      .setUserCustomData(customData)
      .setUser(raygun.user(request) || _user)
      .setVersion(_version)
      .setTags(tags);

    var message = builder.build();
    if (raygun.onBeforeSend) {
      message = typeof _onBeforeSend === 'function' ? _onBeforeSend(message, exception, customData, request, tags) : message;
    }

    var transportMessage = {
      message: message,
      apiKey: _apiKey,
      callback: callback,
      host: _host,
      port: _port,
      useSSL: _useSSL
    };

    if(_isOnline) {
      raygunTransport.send(transportMessage);
    } else {
      _offlineStorage.save(transportMessage);
    }

    return message;
  };

  raygun.expressHandler = function (err, req, res, next) {
    raygun.send(err, {}, function () {}, req);
    next(err);
  };
};

exports.Client = Raygun;
<commit_msg>Flip to _isOffline, easier checking<commit_after>/*jshint unused:vars */

/*
 * raygun
 * https://github.com/MindscapeHQ/raygun4node
 *
 * Copyright (c) 2015 MindscapeHQ
 * Licensed under the MIT license.
 */

'use strict';

var raygunTransport = require('./raygun.transport');
var MessageBuilder = require('./raygun.messageBuilder');
var offlineStorage = require('./raygun.offline');

var Raygun = function () {
  var _apiKey, _filters, raygun = this, _user, _version, _host, _port, _useSSL, _onBeforeSend, _offlineStorage, _isOffline, _cachePath;

  raygun.init = function (options) {
    _apiKey = options.apiKey;
    _filters = options.filters;
    _host = options.host;
    _port = options.port;
    _useSSL = options.useSSL || true;
    _onBeforeSend = options.onBeforeSend;
    _offlineStorage = options.offlineStorage === undefined ? new offlineStorage() : options.offlineStorage;
    _isOffline = options.isOffline;
    _cachePath = options.cachePath;

    if(_isOffline) {
      if(!_cachePath) {
        throw new Error("Cache Path must be defined before Raygun can cache offline");
      }

      _offlineStorage.init(_cachePath);
    }

    return raygun;
  };

  raygun.user = function (req) {
    return;
  };

  // This function is deprecated, is provided for legacy apps and will be
  // removed in 1.0: use raygun.user instead
  raygun.setUser = function (user) {
    _user = user;
    return raygun;
  };

  raygun.setVersion = function (version) {
    _version = version;
    return raygun;
  };

  raygun.onBeforeSend = function (onBeforeSend) {
    _onBeforeSend = onBeforeSend;
    return raygun;
  };

  raygun.offline = function() {
      if(!_cachePath) {
        throw new Error("Cache Path must be defined before Raygun can cache offline");
      }

      _offlineStorage.init(_cachePath);
      _isOffline = true;
  };

  raygun.online = function(callback) {
    _isOffline = false;
    _offlineStorage.send(callback);
  };

  raygun.send = function (exception, customData, callback, request, tags) {
    var builder = new MessageBuilder({ filters: _filters })
      .setErrorDetails(exception)
      .setRequestDetails(request)
      .setMachineName()
      .setEnvironmentDetails()
      .setUserCustomData(customData)
      .setUser(raygun.user(request) || _user)
      .setVersion(_version)
      .setTags(tags);

    var message = builder.build();
    if (raygun.onBeforeSend) {
      message = typeof _onBeforeSend === 'function' ? _onBeforeSend(message, exception, customData, request, tags) : message;
    }

    var transportMessage = {
      message: message,
      apiKey: _apiKey,
      callback: callback,
      host: _host,
      port: _port,
      useSSL: _useSSL
    };

    if(_isOffline) {
      _offlineStorage.save(transportMessage);
    } else {
      raygunTransport.send(transportMessage);
    }

    return message;
  };

  raygun.expressHandler = function (err, req, res, next) {
    raygun.send(err, {}, function () {}, req);
    next(err);
  };
};

exports.Client = Raygun;
<|endoftext|>


<commit_before>/*jshint unused:vars */

/*
 * raygun
 * https://github.com/MindscapeHQ/raygun4node
 *
 * Copyright (c) 2015 MindscapeHQ
 * Licensed under the MIT license.
 */

'use strict';

var raygunTransport = require('./raygun.transport');
var MessageBuilder = require('./raygun.messageBuilder');

var Raygun = function () {
  var _apiKey, _filters, raygun = this, _user, _version;

  raygun.init = function (options) {
    _apiKey = options.apiKey;
    _filters = options.filters;
    return raygun;
  };

  raygun.user = function (req) {
    return;
  };

  // This function is deprecated, is provided for legacy apps and will be
  // removed in 1.0: use raygun.user instead
  raygun.setUser = function (user) {
    _user = user;
    return raygun;
  };

  raygun.setVersion = function (version) {
    _version = version;
    return raygun;
  };

  raygun.send = function (exception, customData, callback, request, tags) {
    var builder = new MessageBuilder({ filters: _filters })
      .setErrorDetails(exception)
      .setRequestDetails(request)
      .setMachineName()
      .setEnvironmentDetails()
      .setUserCustomData(customData)
      .setUser(raygun.user(request) || _user)
      .setVersion(_version)
      .setTags(tags);

    var message = builder.build();
    raygunTransport.send({ message: message, apiKey: _apiKey, callback: callback });
    return message;
  };

  raygun.expressHandler = function (err, req, res, next) {
    raygun.send(err, {}, function () {}, req);
    next(err);
  };
};

exports.Client = Raygun;
<commit_msg>Add onBeforeSend function, like raygun4js<commit_after>/*jshint unused:vars */

/*
 * raygun
 * https://github.com/MindscapeHQ/raygun4node
 *
 * Copyright (c) 2015 MindscapeHQ
 * Licensed under the MIT license.
 */

'use strict';

var raygunTransport = require('./raygun.transport');
var MessageBuilder = require('./raygun.messageBuilder');

var Raygun = function () {
  var _apiKey, _filters, raygun = this, _user, _version, _onBeforeSend;

  raygun.init = function (options) {
    _apiKey = options.apiKey;
    _filters = options.filters;
    return raygun;
  };

  raygun.user = function (req) {
    return;
  };

  // This function is deprecated, is provided for legacy apps and will be
  // removed in 1.0: use raygun.user instead
  raygun.setUser = function (user) {
    _user = user;
    return raygun;
  };

  raygun.setVersion = function (version) {
    _version = version;
    return raygun;
  };

  raygun.onBeforeSend = function (onBeforeSend) {
    _onBeforeSend = onBeforeSend;
    return raygun;
  };

  raygun.send = function (exception, customData, callback, request, tags) {
    var builder = new MessageBuilder({ filters: _filters })
      .setErrorDetails(exception)
      .setRequestDetails(request)
      .setMachineName()
      .setEnvironmentDetails()
      .setUserCustomData(customData)
      .setUser(raygun.user(request) || _user)
      .setVersion(_version)
      .setTags(tags);

    var message = builder.build();
    if (raygun.onBeforeSend)
      message = typeof _onBeforeSend === 'function' ? _onBeforeSend(message, exception, customData) : message;
    raygunTransport.send({ message: message, apiKey: _apiKey, callback: callback });
    return message;
  };

  raygun.expressHandler = function (err, req, res, next) {
    raygun.send(err, {}, function () {}, req);
    next(err);
  };
};

exports.Client = Raygun;
<|endoftext|>


<commit_before>var Record = function(data) {

  if(data.attributes) {
    this.attributes = data.attributes;
    delete data.attributes;
  }

  this.fieldValues = {}
  //this._data = data;

  var data = data;

  var key;

  for(key in data) {
    (function (record, key, data) {
      record.__defineGetter__(key, function() {
        return data[key];
      });
      record.__defineSetter__(key, function(val) {
        data[key] = val;
        record.fieldValues[key] = val;
      });
    }(this, key, data));
  }
}

Record.prototype.getFieldValues = function() {
  var fvs = this.fieldValues;
  for (var fieldKey in this) {
    if(fieldKey !== 'attributes' && fieldKey !== 'Id' && fieldKey !== 'id'
      && fieldKey.substring(0,1) !== '_' && fieldKey !== 'fieldValues'
      && !this.__lookupGetter__(fieldKey) && typeof this[fieldKey] !== 'function') {
      if(!fvs[fieldKey]) {
        fvs[fieldKey] = this[fieldKey];
      }
    }
  }
  return fvs;
}

Record.prototype.getId = function() {
  if(this.Id) {
    return this.Id;
  } else if(this.id) {
    return this.id;
  } else if(this.attributes.url) {
    var url = this.attributes.url;
    return url.substr(url.lastIndexOf('/')+1);
  }
}

module.exports = Record;<commit_msg>cleaned record up<commit_after>var Record = function(data) {

  if(data.attributes) {
    this.attributes = data.attributes;
    delete data.attributes;
  }

  this.fieldValues = {}

  var data = data;
  var key;

  for(key in data) {
    (function (record, key, data) {
      record.__defineGetter__(key, function() {
        return data[key];
      });
      record.__defineSetter__(key, function(val) {
        data[key] = val;
        record.fieldValues[key] = val;
      });
    }(this, key, data));
  }
}

Record.prototype.getFieldValues = function() {
  var fvs = this.fieldValues;
  for (var fieldKey in this) {
    if(fieldKey !== 'attributes' && fieldKey !== 'Id' && fieldKey !== 'id'
      && fieldKey.substring(0,1) !== '_' && fieldKey !== 'fieldValues'
      && !this.__lookupGetter__(fieldKey) && typeof this[fieldKey] !== 'function') {
      if(!fvs[fieldKey]) {
        fvs[fieldKey] = this[fieldKey];
      }
    }
  }
  return fvs;
}

Record.prototype.getId = function() {
  if(this.Id) {
    return this.Id;
  } else if(this.id) {
    return this.id;
  } else if(this.attributes.url) {
    var url = this.attributes.url;
    return url.substr(url.lastIndexOf('/')+1);
  }
}

module.exports = Record;<|endoftext|>


<commit_before>module.exports = function reload (app, opts, server) {
  // Requires
  var path = require('path')
  var fs = require('fs')

  // Parameters variables
  var httpServerOrPort
  var expressApp
  var verboseLogging
  var port

  // Application variables
  var RELOAD_FILE = path.join(__dirname, './reload-client.js')
  var reloadCode = fs.readFileSync(RELOAD_FILE, 'utf8')
  var route

  // Websocket server variables
  var ws = require('ws')
  var WebSocketServer = ws.Server
  var wss

  // General variables
  var socketPortSpecified
  var argumentZero = arguments[0]
  var reloadJsMatch

  opts = opts || {}

  if (arguments.length > 0 && (typeof (argumentZero) === 'object' || typeof (argumentZero) === 'function')) {
    if (typeof (argumentZero) === 'object') { // If old arguments passed handle old arguments, the old arguments and their order were: httpServerOrPort, expressApp, and verboseLogging
      console.warn('Deprecated Warning: You supplied reload old arguments, please upgrade to the new parameters see: https://github.com/jprichardson/reload/tree/master#api-for-express')
      if (arguments.length < 2) {
        throw new TypeError('Lack of/invalid arguments provided to reload. It is recommened to update to the new arguments anyways, this would be a good time to do so.', 'reload.js', 7)
      }
      httpServerOrPort = argumentZero
      expressApp = arguments[1]
      verboseLogging = arguments[2]
      route = '/reload/reload.js'
    } else { // Setup options or use defaults
      expressApp = argumentZero
      port = opts.port || 9856

      route = opts.route
      if (route) {
        // If reload.js is found in the route option strip it. We will concat it for user to ensure no case errors or order problems.
        reloadJsMatch = route.match(/reload\.js/i)
        if (reloadJsMatch) {
          route = route.split(reloadJsMatch)[0]
        }

        /*
         * Concat their provided path (minus `reload.js` if they specified it) with a `/` if they didn't provide one and `reload.js. This allows for us to ensure case, order, and use of `/` is correct
         * For example these route's are all valid:
         * 1. `newRoutePath` -> Their route + `/` + reload.js
         * 2. `newRoutePath/` -> Their route + reload.js
         * 3. `newRoutePath/reload.js` -> (Strip reload.js above) so now: Their route + reload.js
         * 4. `newRoutePath/rEload.js` -> (Strip reload.js above) so now: Their route + reload.js
         * 5. `newRoutePathreload.js` -> (Strip reload.js above) so now: Thier route + `/` + reload.js
         * 6. `newRoutePath/reload.js/rEload.js/... reload.js n number of times -> (Strip above removes all reload.js occurences at the end of the specified route) so now: Their route + 'reload.js`
        */
        route = route + (route.slice(-1) === '/' ? '' : '/') + 'reload.js'
      } else {
        route = '/reload/reload.js'
      }

      verboseLogging = opts.verbose === true || opts.verbose === 'true' || false

      if (port) {
        socketPortSpecified = port
        httpServerOrPort = port
      }

      if (server) {
        socketPortSpecified = null
        httpServerOrPort = server
      }
    }
  } else {
    throw new TypeError('Lack of/invalid arguments provided to reload', 'reload.js', 7)
  }

  // Application setup
  if (verboseLogging) {
    reloadCode = reloadCode.replace('verboseLogging = false', 'verboseLogging = true')
  }
  reloadCode = reloadCode.replace('window.location.origin.replace()', 'window.location.origin.replace(/(^http(s?):\\/\\/)(.*:)(.*)/,' + (socketPortSpecified ? '\'ws$2://$3' + socketPortSpecified : '\'ws$2://$3$4') + '\')')

  expressApp.get(route, function (req, res) {
    res.type('text/javascript')
    res.send(reloadCode)
  })

  // Websocket server setup
  // Use custom user specified port
  if (socketPortSpecified) {
    wss = new WebSocketServer({ port: httpServerOrPort })
  } else { // Attach to server, using server's port. Kept here to support legacy arguments.
    wss = new WebSocketServer({ server: httpServerOrPort })
  }

  wss.on('connection', (ws) => {
    if (verboseLogging) {
      console.log('Reload client connected to server')
    }
  })

  function sendMessage (message) {
    if (verboseLogging) {
      console.log('Sending message to ' + (wss.clients.size) + ' connection(s): ' + message)
    }

    wss.clients.forEach(function each (client) {
      if (client.readyState === ws.OPEN) {
        client.send(message)
      }
    })
  }
  // Return an object, right now it contains only a function reload. When this function is called it calls the `sendMessage` function with the message 'reload' which reloads all connected clients.
  return {
    'reload': function () {
      sendMessage('reload')
    }
  }
}
<commit_msg>Updated deprecated warning link<commit_after>module.exports = function reload (app, opts, server) {
  // Requires
  var path = require('path')
  var fs = require('fs')

  // Parameters variables
  var httpServerOrPort
  var expressApp
  var verboseLogging
  var port

  // Application variables
  var RELOAD_FILE = path.join(__dirname, './reload-client.js')
  var reloadCode = fs.readFileSync(RELOAD_FILE, 'utf8')
  var route

  // Websocket server variables
  var ws = require('ws')
  var WebSocketServer = ws.Server
  var wss

  // General variables
  var socketPortSpecified
  var argumentZero = arguments[0]
  var reloadJsMatch

  opts = opts || {}

  if (arguments.length > 0 && (typeof (argumentZero) === 'object' || typeof (argumentZero) === 'function')) {
    if (typeof (argumentZero) === 'object') { // If old arguments passed handle old arguments, the old arguments and their order were: httpServerOrPort, expressApp, and verboseLogging
      console.warn('Deprecated Warning: You supplied reload old arguments, please upgrade to the new parameters see: https://github.com/alallier/reload/tree/master#api-for-express')
      if (arguments.length < 2) {
        throw new TypeError('Lack of/invalid arguments provided to reload. It is recommened to update to the new arguments anyways, this would be a good time to do so.', 'reload.js', 7)
      }
      httpServerOrPort = argumentZero
      expressApp = arguments[1]
      verboseLogging = arguments[2]
      route = '/reload/reload.js'
    } else { // Setup options or use defaults
      expressApp = argumentZero
      port = opts.port || 9856

      route = opts.route
      if (route) {
        // If reload.js is found in the route option strip it. We will concat it for user to ensure no case errors or order problems.
        reloadJsMatch = route.match(/reload\.js/i)
        if (reloadJsMatch) {
          route = route.split(reloadJsMatch)[0]
        }

        /*
         * Concat their provided path (minus `reload.js` if they specified it) with a `/` if they didn't provide one and `reload.js. This allows for us to ensure case, order, and use of `/` is correct
         * For example these route's are all valid:
         * 1. `newRoutePath` -> Their route + `/` + reload.js
         * 2. `newRoutePath/` -> Their route + reload.js
         * 3. `newRoutePath/reload.js` -> (Strip reload.js above) so now: Their route + reload.js
         * 4. `newRoutePath/rEload.js` -> (Strip reload.js above) so now: Their route + reload.js
         * 5. `newRoutePathreload.js` -> (Strip reload.js above) so now: Thier route + `/` + reload.js
         * 6. `newRoutePath/reload.js/rEload.js/... reload.js n number of times -> (Strip above removes all reload.js occurences at the end of the specified route) so now: Their route + 'reload.js`
        */
        route = route + (route.slice(-1) === '/' ? '' : '/') + 'reload.js'
      } else {
        route = '/reload/reload.js'
      }

      verboseLogging = opts.verbose === true || opts.verbose === 'true' || false

      if (port) {
        socketPortSpecified = port
        httpServerOrPort = port
      }

      if (server) {
        socketPortSpecified = null
        httpServerOrPort = server
      }
    }
  } else {
    throw new TypeError('Lack of/invalid arguments provided to reload', 'reload.js', 7)
  }

  // Application setup
  if (verboseLogging) {
    reloadCode = reloadCode.replace('verboseLogging = false', 'verboseLogging = true')
  }
  reloadCode = reloadCode.replace('window.location.origin.replace()', 'window.location.origin.replace(/(^http(s?):\\/\\/)(.*:)(.*)/,' + (socketPortSpecified ? '\'ws$2://$3' + socketPortSpecified : '\'ws$2://$3$4') + '\')')

  expressApp.get(route, function (req, res) {
    res.type('text/javascript')
    res.send(reloadCode)
  })

  // Websocket server setup
  // Use custom user specified port
  if (socketPortSpecified) {
    wss = new WebSocketServer({ port: httpServerOrPort })
  } else { // Attach to server, using server's port. Kept here to support legacy arguments.
    wss = new WebSocketServer({ server: httpServerOrPort })
  }

  wss.on('connection', (ws) => {
    if (verboseLogging) {
      console.log('Reload client connected to server')
    }
  })

  function sendMessage (message) {
    if (verboseLogging) {
      console.log('Sending message to ' + (wss.clients.size) + ' connection(s): ' + message)
    }

    wss.clients.forEach(function each (client) {
      if (client.readyState === ws.OPEN) {
        client.send(message)
      }
    })
  }
  // Return an object, right now it contains only a function reload. When this function is called it calls the `sendMessage` function with the message 'reload' which reloads all connected clients.
  return {
    'reload': function () {
      sendMessage('reload')
    }
  }
}
<|endoftext|>


<commit_before>var
   fs = require("fs"),
   Component = require("./Component.js"),
   doT = require("dot"),
   requirejs = require('requirejs'),
   path = require("path"),
   nodeCrypto = require("crypto"),
   async = require("async"),
   domParser = new (require("tensor-xmldom").DOMParser)(),
   jsBlock = doT.template(fs.readFileSync(path.resolve(__dirname, "tpl/js.dot"), "utf8")),
   cssBlock = doT.template(fs.readFileSync(path.resolve(__dirname, "tpl/css.dot"), "utf8"));

var Render = function(config, req){
   this.req = req;
   this.config = config;
   this.cache = {};
};

Render.prototype.render = function(viewPath, options, fn){
   var
      self = this,
      dependencies = ["js!utils"],
      hash;

   this._prepareOptions(options, function(err, include, options){
      if (!err){

         dependencies = dependencies.concat(include.sort());
         hash = self._createHash(dependencies);

         if (!self.config["debug"]) {
            self._preparePackages(dependencies, function(err, hash){
               if (!err){
                  self._render(viewPath, options, fn, dependencies, hash);
               }
               else{
                  fn(err);
               }
            });
         }
         else{
            self._render(viewPath, options, fn, dependencies);
         }
      }
      else{
         throw err;
      }
   });
};

Render.prototype._prepareOptions = function(options, cb){
   var
      include = [],
      markup = [],
      helpObj = {};

   for (var i in options){
      if (options.hasOwnProperty(i)){
         if (options[i] instanceof Component){
            include.push("js!" + options[i].getName());
            markup.push(domParser.parseFromString(options[i].toString()).firstChild);
            helpObj[markup.length - 1] = i;
         }
      }
   }
   if (this.config["debug"]){
      cb(null, include, options)
   }
   else{
      async.map(markup, this._getMarkup.bind(this), function(err, results){
         if (!err){
            results.forEach(function(markup, index){
               options[helpObj[index]] = markup.toString();
            });
            cb(null, include, options);
         }
         else{
            cb(err);
         }
      });
   }
};

Render.prototype._getMarkup = function(placeholder, cb){
   var
      self = this,
      componentName = placeholder.getAttribute("data-component");

   this.req(["js!" + componentName], function(ctor){
      var options = ctor.prototype._parseCfg(ctor.prototype._options, placeholder);
      try{
         if (ctor.prototype._dotTplFn){
            var
               html = ctor.prototype._dotTplFn(options),
               elem = domParser.parseFromString(html).firstChild,
               res = ctor.prototype._prepareContainer(placeholder, elem),
               innerComponents = self._getInnerComponents(res);

            if (innerComponents.length){
               async.map(innerComponents, self._getMarkup.bind(self), function(err, result){
                  if(err){
                     cb(err);
                  }
                  else{
                     result.forEach(function(element, i){
                        innerComponents[i].parentNode.replaceChild(element, innerComponents[i]);
                     });
                     cb(null, res);
                  }
               })
            }
            else{
               cb(null, res)
            }
         }
      }
      catch (e){
         cb(e);
      }
   });
};

Render.prototype._getInnerComponents = function(root){
   var
      liveCollection = root.getElementsByTagName("component"),
      deadCollection = [];

   for (var i = 0, l = liveCollection.length; i < l; i++){
      deadCollection.push(liveCollection[i]);
   }
   return deadCollection;
};

Render.prototype._preparePackages = function(dependencies, cb){
   var
      self = this,
      hash = this._createHash(dependencies);
   if (hash in self.cache){
      switch (self.cache[hash]){
         case "ready" :
            cb(null, hash);
            break;
         case "inProgress" :
            cb(null);
            break;
      }
   }
   else{
      self.cache[hash] = "inProgress";
      requirejs.optimize({
         mainConfigFile : path.join(self.config["sf_client"], "main-server.js"),
         separateCSS : true,
         include : dependencies,
         out : path.join(self.config["sf_build"], hash) + ".js",
         mode: "optimization"
      }, function(){
         self.cache[hash] = "ready";
         cb(null, hash);
      }, function(err){
         cb(err);
      });
   }
};

Render.prototype._render = function(viewPath, options, fn, dependencies, hash){
   options["jsBlock"] = jsBlock({
      hash : hash || null,
      dependencies : JSON.stringify(dependencies)
   });
   options["cssBlock"] = cssBlock({
      hash : hash || null
   });

   fs.readFile(viewPath, "utf8", function(err, file){
      if (!err){
         fn(null, doT.template(file)(options));
      }
      else{
         fn(err);
      }
   });
};

Render.prototype._createHash = function(deps){
   var md5 = nodeCrypto.createHash('md5');
   md5.update(deps.join(","));
   return md5.digest('hex');
};

module.exports = Render;
<commit_msg>render.js - support new way of configuration<commit_after>var
   fs = require("fs"),
   Component = require("./Component.js"),
   doT = require("dot"),
   requirejs = require('requirejs'),
   path = require("path"),
   nodeCrypto = require("crypto"),
   async = require("async"),
   jsBlock = doT.template(fs.readFileSync(path.resolve(__dirname, "tpl/js.dot"), "utf8")),
   cssBlock = doT.template(fs.readFileSync(path.resolve(__dirname, "tpl/css.dot"), "utf8"));

var Render = function(config, req){
   this.req = req;
   this.config = config;
   this.cache = {};
};

Render.prototype.render = function(viewPath, options, fn){
   var
      self = this,
      dependencies = ["js!utils"],
      hash;

   this._prepareOptions(options, function(err, include, options){
      if (!err){

         dependencies = dependencies.concat(include.sort());
         hash = self._createHash(dependencies);

         if (!self.config["debug"]) {
            self._preparePackages(dependencies, function(err, hash){
               if (!err){
                  self._render(viewPath, options, fn, dependencies, hash);
               }
               else{
                  fn(err);
               }
            });
         }
         else{
            self._render(viewPath, options, fn, dependencies);
         }
      }
      else{
         throw err;
      }
   });
};

Render.prototype._prepareOptions = function(options, cb){
   var
      include = [],
      markup = [],
      helpObj = {};

   for (var i in options){
      if (options.hasOwnProperty(i)){
         if (options[i] instanceof Component){
            include.push("js!" + options[i].getName());
            markup.push(options[i].toString());
            helpObj[markup.length - 1] = i;
         }
      }
   }
   if (this.config["debug"]){
      cb(null, include, options)
   }
   else{
      async.map(markup, this._getMarkup.bind(this), function(err, results){
         if (!err){
            results.forEach(function(markup, index){
               options[helpObj[index]] = markup;
            });
            cb(null, include, options);
         }
         else{
            cb(err);
         }
      });
   }
};

Render.prototype._getMarkup = function(markup, cb){
   this.req(['js!BaseComponent'], function(BaseComponent){
      try{
         cb(null, BaseComponent.prototype._prepareMarkup(markup));
      }
      catch(e){
         cb(e);
      }
   });
};

Render.prototype._preparePackages = function(dependencies, cb){
   var
      self = this,
      hash = this._createHash(dependencies);
   if (hash in self.cache){
      switch (self.cache[hash]){
         case "ready" :
            cb(null, hash);
            break;
         case "inProgress" :
            cb(null);
            break;
      }
   }
   else{
      self.cache[hash] = "inProgress";
      requirejs.optimize({
         mainConfigFile : path.join(self.config["sf_client"], "main-server.js"),
         separateCSS : true,
         include : dependencies,
         out : path.join(self.config["sf_build"], hash) + ".js",
         mode: "optimization"
      }, function(){
         self.cache[hash] = "ready";
         cb(null, hash);
      }, function(err){
         cb(err);
      });
   }
};

Render.prototype._render = function(viewPath, options, fn, dependencies, hash){
   options["jsBlock"] = jsBlock({
      hash : hash || null,
      dependencies : JSON.stringify(dependencies)
   });
   options["cssBlock"] = cssBlock({
      hash : hash || null
   });

   fs.readFile(viewPath, "utf8", function(err, file){
      if (!err){
         fn(null, doT.template(file)(options));
      }
      else{
         fn(err);
      }
   });
};

Render.prototype._createHash = function(deps){
   var md5 = nodeCrypto.createHash('md5');
   md5.update(deps.join(","));
   return md5.digest('hex');
};

module.exports = Render;
<|endoftext|>


<commit_before>Router.configure({
	layoutTemplate: 'layout',
	loadingTemplate: 'blank',
	notFoundTemplate: 'notFound',
	waitOn: function() {
		return Meteor.subscribe("homepage");
	},
});

CustomController = RouteController.extend({
	onBeforeAction: function(){
		const page_type = Router.current().url.match(/(work|post|about)/)[0];
	  var link_type;

	  if(page_type === "post"){
	    link_type = "blog";
	  } else if (page_type === "work"){
	    link_type = "works";
	  } else if (page_type === "about"){
	    link_type = "about";
	  }

	  $("#link_list a").removeClass("active");
	  $("#collapse_button ul a").removeClass("active");
	  $("#collapse_button ul a[class=" + link_type + "_link]").addClass("active");
	  $("#link_list a[class=" + link_type + "_link]").addClass("active");

		this.next();
	}
});

Router.route("/posts",{
	name: 'posts',
	controller: 'CustomController'
});

Router.route("/posts/view/:post_title",{
	name:'postView',
	controller: 'CustomController'
})

Router.route("/works/view/:work_title",{
	name:'workView',
	controller: 'CustomController'
})

Router.route("/posts/view/:post_title/edit",{
	name:'editPost',
	controller: 'CustomController',
	action: function(){
		this.render("authentication");
	},
})

Router.route("/works/view/:work_title/edit",{
	name:'editWork',
	controller: 'CustomController',
	action: function(){
		this.render("authentication");
	},
})

Router.route("/post_new",{
	controller: 'CustomController',
	action: function(){
		this.render("authentication");
	},
})

Router.route("/work_new",{
	name: 'newWork',
	controller: 'CustomController',
	action: function(){
		this.render("authentication");
	},
})

Router.route("/works", {
	name: 'works',
	controller: 'CustomController'
})

Router.route("/about", {
	name: 'about',
	controller: 'CustomController'
})

Router.route("/about/edit", {
	controller: 'CustomController',
	action: function(){
		this.render("authentication");
	},
})
<commit_msg>No longer use a custom controller<commit_after>Router.configure({
	layoutTemplate: 'layout',
	loadingTemplate: 'blank',
	notFoundTemplate: 'notFound',
	waitOn: function() {
		return Meteor.subscribe("homepage");
	},
	onBeforeAction: function(){
		if (Meteor.isClient){
			const page_type = Router.current().url.match(/(work|post|about)/)[0];
		  var link_type;

		  if(page_type === "post"){
		    link_type = "blog";
		  } else if (page_type === "work"){
		    link_type = "works";
		  } else if (page_type === "about"){
		    link_type = "about";
		  }

		  $("#link_list a").removeClass("active");
		  $("#collapse_button ul a").removeClass("active");
		  $("#collapse_button ul a[class=" + link_type + "_link]").addClass("active");
		  $("#link_list a[class=" + link_type + "_link]").addClass("active");
		}

		this.next();
	},
});

Router.route("/posts",{
	name: 'posts'
});

Router.route("/posts/view/:post_title",{
	name:'postView'
})

Router.route("/works/view/:work_title",{
	name:'workView'
})

Router.route("/posts/view/:post_title/edit",{
	name:'editPost',
	action: function(){
		this.render("authentication");
	},
})

Router.route("/works/view/:work_title/edit",{
	name:'editWork',
	action: function(){
		this.render("authentication");
	},
})

Router.route("/post_new",{
	action: function(){
		this.render("authentication");
	},
})

Router.route("/work_new",{
	name: 'newWork',
	action: function(){
		this.render("authentication");
	},
})

Router.route("/works", {
	name: 'works'
})

Router.route("/about", {
	name: 'about'
})

Router.route("/about/edit", {
	action: function(){
		this.render("authentication");
	},
})
<|endoftext|>


<commit_before>'use strict';

var BearerToken = require('./bearer');
var Inflector = require('inflected');
var Mutation = require('./mutation');

var REST = {
  index: { endpoint: '/', method: 'get' },
  new: { endpoint: '/', method: 'post' },
  show: { endpoint: '/:id', method: 'get' },
  update: { endpoint: '/:id', method: 'put' },
  delete: { endpoint: '/:id', method: 'delete' }
};

var Routes = function(express, DS) {
  var Builder = { express: express };
  var middleware = [];
  var controllers = [];
  var DS = {};
  var Scopes = {};
  var Instance = {};

  Builder.mount = function(models, instanceObj) {
    DS = models;
    Instance = instanceObj;

    for (var i = 0; i < controllers.length; i++) {
      Builder.route(controllers[i](DS, Mutation(DS, Instance)));
    }
  };

  Builder.addController = function(controller) {
    controllers.push(controller);
  };

  Builder.registerMiddleware = function(func) {
    middleware.push(func);
  };

  Builder.mixInController = function(spec, action, rest) {
    return function(req, res, next) {
      var resource = Inflector.singularize(Inflector.camelize(req.route.path.split('/')[1]));

      req.controller = {
        spec: spec,
        action: action,
        permissions: Instance.scope[resource],
        model: resource,
        _rest: rest
      };

      next();
    };
  };

  Builder.runMiddlewares = function(req, res, next) {
    if (middleware.length < 1) return next();

    for (var i = 0; i < middleware.length; i++) {
      middleware[i](req, res, next);
    }
  };

  // Sets an individual route
  Builder.route = function(controller) {
    controller._expose.forEach(function(el, i, arr) {
      if (typeof el === 'object') Builder.customRoute(controller._verb, el, controller[el.name]);
      if (typeof el === 'string') Builder.restRoute(controller._verb, el, controller[el]);
    });
  };

  // Handles a non-RESTful controller method
  Builder.customRoute = function(verb, spec, action) {
    if (!spec || !action) return;

    var method = spec.method;
    var endpoint = '/' + verb;

    if (spec.endpoint !== '/') endpoint += '/' + spec.endpoint;

    express[method](
      endpoint,
      Builder.mixInController(spec, action, false),
      BearerToken.serialize,
      Builder.findUser,
      Builder.catchScope,
      Builder.runMiddlewares,
      action.responder
    );
  };

  // Handles a RESTful controller method
  Builder.restRoute = function(verb, name, action) {
    if (!REST[name] || !action) return;

    var method = REST[name].method;
    var endpoint = '/' + verb;

    if (REST[name].endpoint !== '/') endpoint += REST[name].endpoint;

    express[method](
      endpoint,
      Builder.mixInController({ verb: verb, name: name }, action, true),
      BearerToken.serialize,
      Builder.findUser,
      Builder.resourceQuery,
      Builder.runQuery,
      Builder.catchScope,
      Builder.filterAllowedFields,
      Builder.runMiddlewares,
      action.responder
    );
  };

  Builder.findUser = function(req, res, next) {
    if (!req.bearerToken) return next();

    var User = DS[Instance.scopable.model];
    var query = User.findOne({ _id: req.bearerToken.user });

    query.lean().exec(function(err, user) {
      if (err) return res.status(500).send({ error: err });

      req.user = user;
      return next();
    });
  };

  Builder.catchScope = function(req, res, next) {
    var ownership = false;
    var permissions = req.controller.permissions.read;

    req.caughtScope = '*';

    if (req.user) {
      var userScopes = req.user[Instance.scopable.field];
      if (req.controller._rest) ownership = Builder._checkForOwnership(req);
      req.caughtScope = (ownership) ? 'owner' : Builder._highestScope(permissions, userScopes);
    }

    if (permissions === true || permissions[req.caughtScope] === true) {
      req.allowedFields = true;
      return next();
    } else if (Array.isArray(permissions)) {
      req.allowedFields = permissions;
      return next();
    } else if (Array.isArray(permissions[req.caughtScope])) {
      req.allowedFields = permissions[req.caughtScope];
      return next();
    }

    // The user is not authorized to see this resource
    return res.status(401).send({ error: 'Insufficient permission for that' });
  };

  Builder.resourceQuery = function(req, res, next) {
    req.searchParams = {};

    if (!req.params.id && req.controller.permissions.searchableBy) {
      var allowed = req.controller.permissions.searchableBy;

      for (var i = 0; i < allowed.length; i++) {
        if (req.query[allowed[i]]) req.searchParams[allowed[i]] = req.query[allowed[i]];
      }
    }

    return next();
  };

  Builder.runQuery = function(req, res, next) {
    var Model = DS[req.controller.model];
    req.rawResources = {};

    if (req.params.id) {
      var q = Model.findById(req.params.id);

      q.exec(function(err, result) {
        req.rawResources = result;
        return next();
      });
    } else if (!req.params.id && req.method === 'GET') {
      var q = Model.find(req.searchParams).limit(req.query.limit || 20);

      q.lean().exec(function(err, result) {
        req.rawResources = result;
        return next();
      });
    } else {
      return next();
    }
  };

  Builder.filterAllowedFields = function(req, res, next) {
    if (req.allowedFields !== true) req.allowedFields.push('_id');

    if (Array.isArray(req.rawResources)) {
      req.resource = [];
      for (var i = 0; i < req.rawResources.length; i++) {
        req.resource.push(Builder.filterFromResource(req.rawResources[i], req.allowedFields));
      }
    } else {
      req.resource = Builder.filterFromResource(req.rawResources, req.allowedFields);
    }

    next();
  };

  Builder.filterFromResource = function(resource, allowed) {
    if (allowed === true) return resource;
    var safeResource = {};

    for (var i = 0; i < allowed.length; i++) {
      safeResource[allowed[i]] = resource[allowed[i]];
    }

    return safeResource;
  };

  Builder._checkForOwnership = function(req) {
    var model = req.controller.model;
    var permissions = req.controller.permissions;

    if (!permissions.owners || permissions.owners.length < 1) return false;
    if (Array.isArray(req.rawResources)) return false;
    if (!req.user) return false;

    for (var i = 0; i < permissions.owners.length; i++) {
      if (req.rawResources[permissions.owners[i]].toString() === req.user._id.toString()) return true;
    }

    return false;
  };

  Builder._highestScope = function(permissions, scopes) {
    if (typeof permissions === 'undefined') return '*';

    var highest = 0;
    var fieldCount = 0;

    scopes.unshift('*');

    for (var i = 0; i < scopes.length; i++) {
      if (permissions === true || Array.isArray(permissions)) {
        return '*';
      } else if (permissions[scopes[i]] === true) {
        return scopes[i];
      } else if (Array.isArray(permissions[scopes[i]]) && (fieldCount < permissions[scopes[i]].length)) {
        highest = i;
        fieldCount = permissions[scopes[i]].length;
      }
    }

    return scopes[highest];
  };

  return Builder;

};

module.exports = Routes;
<commit_msg>checks if there is a point for ownership check<commit_after>'use strict';

var BearerToken = require('./bearer');
var Inflector = require('inflected');
var Mutation = require('./mutation');

var REST = {
  index: { endpoint: '/', method: 'get' },
  new: { endpoint: '/', method: 'post' },
  show: { endpoint: '/:id', method: 'get' },
  update: { endpoint: '/:id', method: 'put' },
  delete: { endpoint: '/:id', method: 'delete' }
};

var Routes = function(express, DS) {
  var Builder = { express: express };
  var middleware = [];
  var controllers = [];
  var DS = {};
  var Scopes = {};
  var Instance = {};

  Builder.mount = function(models, instanceObj) {
    DS = models;
    Instance = instanceObj;

    for (var i = 0; i < controllers.length; i++) {
      Builder.route(controllers[i](DS, Mutation(DS, Instance)));
    }
  };

  Builder.addController = function(controller) {
    controllers.push(controller);
  };

  Builder.registerMiddleware = function(func) {
    middleware.push(func);
  };

  Builder.mixInController = function(spec, action, rest) {
    return function(req, res, next) {
      var resource = Inflector.singularize(Inflector.camelize(req.route.path.split('/')[1]));

      req.controller = {
        spec: spec,
        action: action,
        permissions: Instance.scope[resource],
        model: resource,
        _rest: rest
      };

      next();
    };
  };

  Builder.runMiddlewares = function(req, res, next) {
    if (middleware.length < 1) return next();

    for (var i = 0; i < middleware.length; i++) {
      middleware[i](req, res, next);
    }
  };

  // Sets an individual route
  Builder.route = function(controller) {
    controller._expose.forEach(function(el, i, arr) {
      if (typeof el === 'object') Builder.customRoute(controller._verb, el, controller[el.name]);
      if (typeof el === 'string') Builder.restRoute(controller._verb, el, controller[el]);
    });
  };

  // Handles a non-RESTful controller method
  Builder.customRoute = function(verb, spec, action) {
    if (!spec || !action) return;

    var method = spec.method;
    var endpoint = '/' + verb;

    if (spec.endpoint !== '/') endpoint += '/' + spec.endpoint;

    express[method](
      endpoint,
      Builder.mixInController(spec, action, false),
      BearerToken.serialize,
      Builder.findUser,
      Builder.catchScope,
      Builder.runMiddlewares,
      action.responder
    );
  };

  // Handles a RESTful controller method
  Builder.restRoute = function(verb, name, action) {
    if (!REST[name] || !action) return;

    var method = REST[name].method;
    var endpoint = '/' + verb;

    if (REST[name].endpoint !== '/') endpoint += REST[name].endpoint;

    express[method](
      endpoint,
      Builder.mixInController({ verb: verb, name: name }, action, true),
      BearerToken.serialize,
      Builder.findUser,
      Builder.resourceQuery,
      Builder.runQuery,
      Builder.catchScope,
      Builder.filterAllowedFields,
      Builder.runMiddlewares,
      action.responder
    );
  };

  Builder.findUser = function(req, res, next) {
    if (!req.bearerToken) return next();

    var User = DS[Instance.scopable.model];
    var query = User.findOne({ _id: req.bearerToken.user });

    query.lean().exec(function(err, user) {
      if (err) return res.status(500).send({ error: err });

      req.user = user;
      return next();
    });
  };

  Builder.catchScope = function(req, res, next) {
    var ownership = false;
    var permissions = req.controller.permissions.read;

    req.caughtScope = '*';

    if (req.user) {
      var userScopes = req.user[Instance.scopable.field];
      if (req.controller._rest && ['index', 'new'].indexOf(req.controller.spec.name) === -1) ownership = Builder._checkForOwnership(req);
      req.caughtScope = (ownership) ? 'owner' : Builder._highestScope(permissions, userScopes);
    }

    if (permissions === true || permissions[req.caughtScope] === true) {
      req.allowedFields = true;
      return next();
    } else if (Array.isArray(permissions)) {
      req.allowedFields = permissions;
      return next();
    } else if (Array.isArray(permissions[req.caughtScope])) {
      req.allowedFields = permissions[req.caughtScope];
      return next();
    }

    // The user is not authorized to see this resource
    return res.status(401).send({ error: 'Insufficient permission for that' });
  };

  Builder.resourceQuery = function(req, res, next) {
    req.searchParams = {};

    if (!req.params.id && req.controller.permissions.searchableBy) {
      var allowed = req.controller.permissions.searchableBy;

      for (var i = 0; i < allowed.length; i++) {
        if (req.query[allowed[i]]) req.searchParams[allowed[i]] = req.query[allowed[i]];
      }
    }

    return next();
  };

  Builder.runQuery = function(req, res, next) {
    var Model = DS[req.controller.model];
    req.rawResources = {};

    if (req.params.id) {
      var q = Model.findById(req.params.id);

      q.exec(function(err, result) {
        req.rawResources = result;
        return next();
      });
    } else if (!req.params.id && req.method === 'GET') {
      var q = Model.find(req.searchParams).limit(req.query.limit || 20);

      q.lean().exec(function(err, result) {
        req.rawResources = result;
        return next();
      });
    } else {
      return next();
    }
  };

  Builder.filterAllowedFields = function(req, res, next) {
    if (req.allowedFields !== true) req.allowedFields.push('_id');

    if (Array.isArray(req.rawResources)) {
      req.resource = [];
      for (var i = 0; i < req.rawResources.length; i++) {
        req.resource.push(Builder.filterFromResource(req.rawResources[i], req.allowedFields));
      }
    } else {
      req.resource = Builder.filterFromResource(req.rawResources, req.allowedFields);
    }

    next();
  };

  Builder.filterFromResource = function(resource, allowed) {
    if (allowed === true) return resource;
    var safeResource = {};

    for (var i = 0; i < allowed.length; i++) {
      safeResource[allowed[i]] = resource[allowed[i]];
    }

    return safeResource;
  };

  Builder._checkForOwnership = function(req) {
    var model = req.controller.model;
    var permissions = req.controller.permissions;

    if (!permissions.owners || permissions.owners.length < 1) return false;
    if (Array.isArray(req.rawResources)) return false;
    if (!req.user) return false;

    for (var i = 0; i < permissions.owners.length; i++) {
      if (typeof req.rawResources[permissions.owners[i]] === 'undefined') return false;
      if (typeof req.user._id === 'undefined') return false;

      if (req.rawResources[permissions.owners[i]].toString() === req.user._id.toString()) return true;
    }

    return false;
  };

  Builder._highestScope = function(permissions, scopes) {
    if (typeof permissions === 'undefined') return '*';

    var highest = 0;
    var fieldCount = 0;

    scopes.unshift('*');

    for (var i = 0; i < scopes.length; i++) {
      if (permissions === true || Array.isArray(permissions)) {
        return '*';
      } else if (permissions[scopes[i]] === true) {
        return scopes[i];
      } else if (Array.isArray(permissions[scopes[i]]) && (fieldCount < permissions[scopes[i]].length)) {
        highest = i;
        fieldCount = permissions[scopes[i]].length;
      }
    }

    return scopes[highest];
  };

  return Builder;

};

module.exports = Routes;
<|endoftext|>