<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);
}

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;

var stdin, stdout;

//
// 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;

  prompt.allowEmpty = options.allowEmpty || false;

  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 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,
      raw    = ['prompt', ': ' + name.grey, ': '.grey],
      read   = prop.hidden ? prompt.readLineHidden : prompt.readLine,
      length, msg;

  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) {
      var valid;

      if (prop.validator) {
        valid = prop.validator.test
         ? prop.validator.test(line)
         : prop.validator(line);
      }

      if (prop.empty === false && valid) {
        valid = line.length > 0;
        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);
      }
    }

    logger.input(line.yellow);
    callback(null, line);
  });

  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'));
      val = 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 = '', buffer = '';
  stdio.setRawMode(true);
  prompt.resume();
  stdin.on('error', callback);
  stdin.on('data', function data (c) {
    c = '' + c;
    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();
        prompt.pause();
        return callback(null, value)
      case '\u0003': case '\0':
        stdout.write('\n');
        process.exit(1);
        break;
      default:
        value += buffer + c
        buffer = '';
        break;
    }
  });

  return prompt;
};<commit_msg>[fix] Remove leaking global variable<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'),
    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);
}

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;

var stdin, stdout;

//
// 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;

  prompt.allowEmpty = options.allowEmpty || false;

  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 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,
      raw    = ['prompt', ': ' + name.grey, ': '.grey],
      read   = prop.hidden ? prompt.readLineHidden : prompt.readLine,
      length, msg;

  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) {
      var valid;

      if (prop.validator) {
        valid = prop.validator.test
         ? prop.validator.test(line)
         : prop.validator(line);
      }

      if (prop.empty === false && valid) {
        valid = line.length > 0;
        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);
      }
    }

    logger.input(line.yellow);
    callback(null, line);
  });

  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 = '', buffer = '';
  stdio.setRawMode(true);
  prompt.resume();
  stdin.on('error', callback);
  stdin.on('data', function data (c) {
    c = '' + c;
    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();
        prompt.pause();
        return callback(null, value)
      case '\u0003': case '\0':
        stdout.write('\n');
        process.exit(1);
        break;
      default:
        value += buffer + c
        buffer = '';
        break;
    }
  });

  return prompt;
};<|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', 'ul'],

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

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

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

  controlsTpl = ' \

 \

Bold
 \

Emphasis
 \

|
 \

Bullets List
 \

Numbered List
 \

Indent
 \

Outdent
 \

|
 \

Link
 \

 \
  ';

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

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

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

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

      execSTRONG: function() {
        document.execCommand('bold', false, true);
        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);
      $(element).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]);
        }, 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;
              self.trigger('changed');
            }, 200);
          }
        }, 10);
      });
    }

    // 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

        $controls.insertBefore(el);
        $controls.show();
        // var offset = $(el).offset();
        // $controls.css('left', offset.left)
        //          .css('top', offset.top-$controls.height()-10);

      }
    };

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

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

    // Bind events for controls
    $('.proper-commands a.command').click(function(e) {
      commands['exec'+ $(e.currentTarget).attr('command').toUpperCase()]();

      setTimeout(function() {
        semantify(activeElement);
        sanitize($(activeElement)[0]);
        self.trigger('changed');
      }, 10);

      return false;
    });

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

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

})();<commit_msg>Contenteditable area no longer looses focus when a command is issued.<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', 'ul'],

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

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

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

  controlsTpl = ' \

 \

Bold
 \

Emphasis
 \

|
 \

Bullets List
 \

Numbered List
 \

Indent
 \

Outdent
 \

|
 \

Link
 \

 \
  ';

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

  this.Proper = function(options) {
    var activeElement = null,     // element that is being edited
        $commands,
        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;
      },

      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
    $('.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;
  };

})();<|endoftext|>


<commit_before>import React from 'react';
import ViewerHelper from './viewer-helper';
import ViewerEvent from './viewer-event';
import cursor from './cursor';
import {calculateBox, mapRange} from './utils';
import Gradient from './gradient';

import {
  TOOL_NONE, TOOL_PAN, TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT,
  MODE_IDLE, MODE_PANNING, MODE_ZOOMING,
  DIRECTION_NONE, DIRECTION_UP, DIRECTION_RIGHT, DIRECTION_DOWN, DIRECTION_LEFT
} from './constants';

export default class Viewer extends React.Component {

  constructor(props) {
    super(props);
    this.handleSpecialKeyChange = this.handleSpecialKeyChange.bind(this);
    this.handleAutoPan = this.handleAutoPan.bind(this);
  }

  shouldComponentUpdate(nextProps) {
    return nextProps !== this.props;
  }

  handleStartPan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange} = this.props;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_IDLE) return;

    let nextValue = ViewerHelper.startPan(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handleUpdatePan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height, children} = this.props;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_PANNING) return;

    //the mouse exited and reentered into svg
    let forceExit = (value.mode === MODE_PANNING && event.buttons === 0);

    let nextValue = forceExit ?
      ViewerHelper.stopPan(value) :
      ViewerHelper.updatePan(value, x, y, 20, SVGWidth, SVGHeight, width, height);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handleStopPan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange} = this.props;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_PANNING) return;

    let nextValue = ViewerHelper.stopPan(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handleStartZoom(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, children} = this.props;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if ([TOOL_ZOOM, TOOL_ZOOM_IN].indexOf(tool) === -1) return;
    if (value.mode !== MODE_IDLE) return;

    //let point = ViewerHelper.getSVGPoint(value, x, y);
    //if(!ViewerHelper.isPointInsideSVG(point.x, point.y, SVGWidth, SVGHeight)) return;

    let nextValue = ViewerHelper.startZoomSelection(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handleUpdateZoom(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height} = this.props;

    if ([TOOL_ZOOM, TOOL_ZOOM_IN].indexOf(tool) === -1) return;
    if (value.mode !== MODE_ZOOMING) return;

    //the mouse exited and reentered into svg
    let forceExit = (event.buttons === 0);

    let nextValue = forceExit ?
      ViewerHelper.stopZoomSelection(value, width, height)
      : ViewerHelper.updateZoomSelection(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handleStopZoom(event) {
    let abs = Math.abs;
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height} = this.props;
    let {startX, endX, startY, endY, specialKeyEnabled} = value;

    if ([TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT].indexOf(tool) === -1) return;
    if (value.mode !== MODE_ZOOMING && tool !== TOOL_ZOOM_OUT) return;

    let selectionMode = abs(startX - endX) > 7 && abs(startY - endY) > 7 && tool !== TOOL_ZOOM_OUT;

    let nextValue;

    if (selectionMode) {
      nextValue = ViewerHelper.stopZoomSelection(value, width, height);
    } else {
      let needZoomIn = (tool === TOOL_ZOOM_IN) || (tool === TOOL_ZOOM && !specialKeyEnabled);
      let scaleFactor = needZoomIn ? 1.1 : 0.9;
      nextValue = ViewerHelper.zoom(value, scaleFactor, x, y);
    }

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handleEvent(event) {
    let {value, tool, onClick, onMouseUp, onMouseMove, onMouseDown} = this.props;
    let eventsHandler = {click: onClick, mousemove: onMouseMove, mouseup: onMouseUp, mousedown: onMouseDown};

    if (tool !== TOOL_NONE) return;
    let onEventHandler = eventsHandler[event.type];
    if (!onEventHandler) return;

    onEventHandler(new ViewerEvent(event, value, this.refs.svg));
  }

  handleSpecialKeyChange(event) {
    let {value, specialKeys, onChange} = this.props;
    let key = event.which;
    let active = event.type === "keydown";

    if (specialKeys.indexOf(key) === -1) return;

    let nextValue = active ? ViewerHelper.enableSpecialKey(value) : ViewerHelper.disableSpecialKey(value);

    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handlePinch(event) {
    let {value, onChange, detectPinch} = this.props;
    if (!detectPinch) return;

    let rect = this.refs.svg.getBoundingClientRect();
    let x = event.clientX - Math.round(rect.left);
    let y = event.clientY - Math.round(rect.top);
    var delta = Math.max(-1, Math.min(1, event.deltaY));
    let scaleFactor = mapRange(delta, -1, 1, 1.06, 0.96);

    let nextValue = ViewerHelper.zoom(value, scaleFactor, x, y);
    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handleAutoPanDetection(event) {
    let {value, onChange, width, height, tool} = this.props;
    if (tool !== TOOL_NONE) return;

    let rect = this.refs.svg.getBoundingClientRect();
    let x = event.clientX - Math.round(rect.left);
    let y = event.clientY - Math.round(rect.top);

    let nextValue = ViewerHelper.updateAutoPan(value, x, y, width, height);
    if (value !== nextValue) onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handleAutoPan() {
    let {value, onChange, tool, detectAutoPan} = this.props;
    let {autoPanX, autoPanY} = value;
    let deltaX = 0, deltaY = 0, delta = 30;

    if (tool !== TOOL_NONE) return;
    if (!value.focus) return;
    if (!detectAutoPan) return;
    if (autoPanX === DIRECTION_NONE && autoPanY === DIRECTION_NONE) return;

    if (autoPanX === DIRECTION_LEFT) {
      deltaX = delta;
    } else if (autoPanX === DIRECTION_RIGHT) {
      deltaX = -delta;
    }

    if (autoPanY === DIRECTION_UP) {
      deltaY = delta
    } else if (autoPanY === DIRECTION_DOWN) {
      deltaY = -delta;
    }

    let nextValue = ViewerHelper.pan(value, deltaX, deltaY);
    onChange(new ViewerEvent(null, nextValue, this.refs.svg));
  }

  handleUpdateFocus(event, focus) {
    let {value, onChange} = this.props;
    let nextValue = ViewerHelper.updateFocus(value, focus);
    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  componentWillMount(event) {
    window.addEventListener("keydown", this.handleSpecialKeyChange, false);
    window.addEventListener("keyup", this.handleSpecialKeyChange, false);
  }

  componentDidMount() {
    this.autoPanTimer = setInterval(this.handleAutoPan, 200);
  }

  componentWillUnmount(event) {
    window.removeEventListener("keydown", this.handleSpecialKeyChange, false);
    window.removeEventListener("keyup", this.handleSpecialKeyChange, false);
    clearTimeout(this.autoPanTimer);
  }

  render() {
    let originalSVG = this.props.children;
    let tool = this.props.tool;
    let {matrix, mode, specialKeyEnabled, autoPanX, autoPanY, focus} = this.props.value;
    let {width: SVGWidth, height: SVGHeight} = this.props;
    let matrixStr = `matrix(${matrix.a}, ${matrix.b}, ${matrix.c}, ${matrix.d}, ${matrix.e}, ${matrix.f})`;

    let style = {};
    let gStyle = tool === TOOL_NONE ? {} : {pointerEvents: "none"};
    if (tool === TOOL_PAN) style.cursor = cursor(mode === MODE_PANNING ? 'grabbing' : 'grab');
    if ([TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT].indexOf(tool) >= 0) {
      let needZoomIn = (tool === TOOL_ZOOM_IN) || (tool === TOOL_ZOOM && !specialKeyEnabled);
      style.cursor = cursor(needZoomIn ? 'zoom-in' : 'zoom-out');
    }

    let zoomSelectionRect;
    if (mode === MODE_ZOOMING) {
      let {startX, startY, endX, endY} = this.props.value;
      let box = calculateBox({x: startX, y: startY}, {x: endX, y: endY});

      zoomSelectionRect =
        <rect
          stroke="#969FFF"
          strokeOpacity={0.7}
          fill="#F3F4FF"
          fillOpacity={0.7}
          x={box.x}
          y={box.y}
          width={box.width}
          height={box.height}/>
    }

    return (
      <svg
        ref="svg"
        width={SVGWidth}
        height={SVGHeight}
        style={Object.assign(style, this.props.style)}
        onMouseDown={ event => {
          this.handleStartPan(event);
          this.handleStartZoom(event)
        } }
        onMouseMove={ event => {
          this.handleUpdatePan(event);
          this.handleUpdateZoom(event);
          this.handleAutoPanDetection(event);
        } }
        onMouseUp={ event => {
          this.handleStopPan(event);
          this.handleStopZoom(event)
        } }
        onWheel={ event => {
          this.handlePinch(event)
        }}
        onMouseEnter={event => {
          this.handleUpdateFocus(event, true);
        }}
        onMouseLeave={event => {
          this.handleUpdateFocus(event, false);
        }}
      >

        <rect
          fill={this.props.background}
          x={0}
          y={0}
          width={this.props.width}
          height={this.props.height}
          style={{pointerEvents: "none"}}
        />

        <g
          ref="originalSVG"
          transform={matrixStr}
          style={gStyle}
          onMouseDown={ event => this.handleEvent(event)}
          onMouseMove={event => this.handleEvent(event)}
          onMouseUp={event => this.handleEvent(event)}
          onClick={event => this.handleEvent(event)}
        >
          <rect
            fill={this.props.SVGBackground}
            x={0}
            y={0}
            width={originalSVG.props.width}
            height={originalSVG.props.height}/>
          <g ref="content">
            {originalSVG.props.children}
          </g>
        </g>

        <g style={{pointerEvents: "none"}}>
        {focus ? <Gradient direction={autoPanX} SVGWidth={SVGWidth} SVGHeight={SVGHeight} /> : null}
        {focus ? <Gradient direction={autoPanY} SVGWidth={SVGWidth} SVGHeight={SVGHeight} /> : null}
        </g>

        {zoomSelectionRect}
      </svg>
    );
  }
}

Viewer.propTypes = {
  //width of the viewer displayed on screen
  width: React.PropTypes.number.isRequired,

  //height of the viewer displayed on screen
  height: React.PropTypes.number.isRequired,

  //background of the viewer
  background: React.PropTypes.string,

  //background of the svg
  SVGBackground: React.PropTypes.string,

  //value of the viewer (current point of view)
  value: React.PropTypes.object.isRequired,

  //CSS style of the SVG tag
  style: React.PropTypes.object,

  //array of keys that in zoom mode switch zoom in and zoom out
  specialKeys: React.PropTypes.arrayOf(React.PropTypes.number),

  //detect zoom operation performed trough pinch gesture or mouse scroll
  detectPinch: React.PropTypes.bool,

  //perform PAN if the mouse is on viewer border
  detectAutoPan: React.PropTypes.bool,

  //handler something changed
  onChange: React.PropTypes.func.isRequired,

  //handler click
  onClick: React.PropTypes.func,

  //handler mouseup
  onMouseUp: React.PropTypes.func,

  //handler mousemove
  onMouseMove: React.PropTypes.func,

  //handler mousedown
  onMouseDown: React.PropTypes.func,

  //current active tool (TOOL_NONE, TOOL_PAN, TOOL_ZOOM)
  tool: React.PropTypes.oneOf([TOOL_NONE, TOOL_PAN, TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT]),

  //accept only one node SVG
  children: function (props, propName, componentName) {
    // Only accept a single child, of the appropriate type
    //credits: http://www.mattzabriskie.com/blog/react-validating-children
    var prop = props[propName];
    var types = ['svg'];
    if (React.Children.count(prop) !== 1 ||
      types.indexOf(prop.type) === -1) {
      return new Error(
        '`' + componentName + '` ' +
        'should have a single child of the following types: ' +
        ' `' + types.join('`, `') + '`.'
      );
    }
    if (!prop.props.hasOwnProperty('width') || !prop.props.hasOwnProperty('height')) {
      return new Error('SVG should have props `width` and `height`');
    }

  }
};

Viewer.defaultProps = {
  style: {},
  background: "#616264",
  SVGBackground: "#fff",
  tool: TOOL_NONE,
  detectPinch: true,
  detectAutoPan: true,
  specialKeys: [91, 17] //91=Win/Cmd 17=Ctrl,
};
<commit_msg>fix gradient visualization<commit_after>import React from 'react';
import ViewerHelper from './viewer-helper';
import ViewerEvent from './viewer-event';
import cursor from './cursor';
import {calculateBox, mapRange} from './utils';
import Gradient from './gradient';

import {
  TOOL_NONE, TOOL_PAN, TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT,
  MODE_IDLE, MODE_PANNING, MODE_ZOOMING,
  DIRECTION_NONE, DIRECTION_UP, DIRECTION_RIGHT, DIRECTION_DOWN, DIRECTION_LEFT
} from './constants';

export default class Viewer extends React.Component {

  constructor(props) {
    super(props);
    this.handleSpecialKeyChange = this.handleSpecialKeyChange.bind(this);
    this.handleAutoPan = this.handleAutoPan.bind(this);
  }

  shouldComponentUpdate(nextProps) {
    return nextProps !== this.props;
  }

  handleStartPan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange} = this.props;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_IDLE) return;

    let nextValue = ViewerHelper.startPan(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handleUpdatePan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height, children} = this.props;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_PANNING) return;

    //the mouse exited and reentered into svg
    let forceExit = (value.mode === MODE_PANNING && event.buttons === 0);

    let nextValue = forceExit ?
      ViewerHelper.stopPan(value) :
      ViewerHelper.updatePan(value, x, y, 20, SVGWidth, SVGHeight, width, height);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handleStopPan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange} = this.props;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_PANNING) return;

    let nextValue = ViewerHelper.stopPan(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handleStartZoom(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, children} = this.props;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if ([TOOL_ZOOM, TOOL_ZOOM_IN].indexOf(tool) === -1) return;
    if (value.mode !== MODE_IDLE) return;

    //let point = ViewerHelper.getSVGPoint(value, x, y);
    //if(!ViewerHelper.isPointInsideSVG(point.x, point.y, SVGWidth, SVGHeight)) return;

    let nextValue = ViewerHelper.startZoomSelection(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handleUpdateZoom(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height} = this.props;

    if ([TOOL_ZOOM, TOOL_ZOOM_IN].indexOf(tool) === -1) return;
    if (value.mode !== MODE_ZOOMING) return;

    //the mouse exited and reentered into svg
    let forceExit = (event.buttons === 0);

    let nextValue = forceExit ?
      ViewerHelper.stopZoomSelection(value, width, height)
      : ViewerHelper.updateZoomSelection(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handleStopZoom(event) {
    let abs = Math.abs;
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height} = this.props;
    let {startX, endX, startY, endY, specialKeyEnabled} = value;

    if ([TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT].indexOf(tool) === -1) return;
    if (value.mode !== MODE_ZOOMING && tool !== TOOL_ZOOM_OUT) return;

    let selectionMode = abs(startX - endX) > 7 && abs(startY - endY) > 7 && tool !== TOOL_ZOOM_OUT;

    let nextValue;

    if (selectionMode) {
      nextValue = ViewerHelper.stopZoomSelection(value, width, height);
    } else {
      let needZoomIn = (tool === TOOL_ZOOM_IN) || (tool === TOOL_ZOOM && !specialKeyEnabled);
      let scaleFactor = needZoomIn ? 1.1 : 0.9;
      nextValue = ViewerHelper.zoom(value, scaleFactor, x, y);
    }

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handleEvent(event) {
    let {value, tool, onClick, onMouseUp, onMouseMove, onMouseDown} = this.props;
    let eventsHandler = {click: onClick, mousemove: onMouseMove, mouseup: onMouseUp, mousedown: onMouseDown};

    if (tool !== TOOL_NONE) return;
    let onEventHandler = eventsHandler[event.type];
    if (!onEventHandler) return;

    onEventHandler(new ViewerEvent(event, value, this.refs.svg));
  }

  handleSpecialKeyChange(event) {
    let {value, specialKeys, onChange} = this.props;
    let key = event.which;
    let active = event.type === "keydown";

    if (specialKeys.indexOf(key) === -1) return;

    let nextValue = active ? ViewerHelper.enableSpecialKey(value) : ViewerHelper.disableSpecialKey(value);

    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handlePinch(event) {
    let {value, onChange, detectPinch} = this.props;
    if (!detectPinch) return;

    let rect = this.refs.svg.getBoundingClientRect();
    let x = event.clientX - Math.round(rect.left);
    let y = event.clientY - Math.round(rect.top);
    var delta = Math.max(-1, Math.min(1, event.deltaY));
    let scaleFactor = mapRange(delta, -1, 1, 1.06, 0.96);

    let nextValue = ViewerHelper.zoom(value, scaleFactor, x, y);
    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handleAutoPanDetection(event) {
    let {value, onChange, width, height, tool} = this.props;
    if (tool !== TOOL_NONE) return;

    let rect = this.refs.svg.getBoundingClientRect();
    let x = event.clientX - Math.round(rect.left);
    let y = event.clientY - Math.round(rect.top);

    let nextValue = ViewerHelper.updateAutoPan(value, x, y, width, height);
    if (value !== nextValue) onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  handleAutoPan() {
    let {value, onChange, tool, detectAutoPan} = this.props;
    let {autoPanX, autoPanY} = value;
    let deltaX = 0, deltaY = 0, delta = 30;

    if (tool !== TOOL_NONE) return;
    if (!value.focus) return;
    if (!detectAutoPan) return;
    if (autoPanX === DIRECTION_NONE && autoPanY === DIRECTION_NONE) return;

    if (autoPanX === DIRECTION_LEFT) {
      deltaX = delta;
    } else if (autoPanX === DIRECTION_RIGHT) {
      deltaX = -delta;
    }

    if (autoPanY === DIRECTION_UP) {
      deltaY = delta
    } else if (autoPanY === DIRECTION_DOWN) {
      deltaY = -delta;
    }

    let nextValue = ViewerHelper.pan(value, deltaX, deltaY);
    onChange(new ViewerEvent(null, nextValue, this.refs.svg));
  }

  handleUpdateFocus(event, focus) {
    let {value, onChange} = this.props;
    let nextValue = ViewerHelper.updateFocus(value, focus);
    onChange(new ViewerEvent(event, nextValue, this.refs.svg));
  }

  componentWillMount(event) {
    window.addEventListener("keydown", this.handleSpecialKeyChange, false);
    window.addEventListener("keyup", this.handleSpecialKeyChange, false);
  }

  componentDidMount() {
    this.autoPanTimer = setInterval(this.handleAutoPan, 200);
  }

  componentWillUnmount(event) {
    window.removeEventListener("keydown", this.handleSpecialKeyChange, false);
    window.removeEventListener("keyup", this.handleSpecialKeyChange, false);
    clearTimeout(this.autoPanTimer);
  }

  render() {
    let originalSVG = this.props.children;
    let {matrix, mode, specialKeyEnabled, autoPanX, autoPanY, focus} = this.props.value;
    let {width: SVGWidth, height: SVGHeight, detectAutoPan, tool} = this.props;
    let matrixStr = `matrix(${matrix.a}, ${matrix.b}, ${matrix.c}, ${matrix.d}, ${matrix.e}, ${matrix.f})`;

    let style = {};
    let gStyle = tool === TOOL_NONE ? {} : {pointerEvents: "none"};
    if (tool === TOOL_PAN) style.cursor = cursor(mode === MODE_PANNING ? 'grabbing' : 'grab');
    if ([TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT].indexOf(tool) >= 0) {
      let needZoomIn = (tool === TOOL_ZOOM_IN) || (tool === TOOL_ZOOM && !specialKeyEnabled);
      style.cursor = cursor(needZoomIn ? 'zoom-in' : 'zoom-out');
    }

    let zoomSelectionRect;
    if (mode === MODE_ZOOMING) {
      let {startX, startY, endX, endY} = this.props.value;
      let box = calculateBox({x: startX, y: startY}, {x: endX, y: endY});

      zoomSelectionRect =
        <rect
          stroke="#969FFF"
          strokeOpacity={0.7}
          fill="#F3F4FF"
          fillOpacity={0.7}
          x={box.x}
          y={box.y}
          width={box.width}
          height={box.height}/>
    }

    return (
      <svg
        ref="svg"
        width={SVGWidth}
        height={SVGHeight}
        style={Object.assign(style, this.props.style)}
        onMouseDown={ event => {
          this.handleStartPan(event);
          this.handleStartZoom(event)
        } }
        onMouseMove={ event => {
          this.handleUpdatePan(event);
          this.handleUpdateZoom(event);
          this.handleAutoPanDetection(event);
        } }
        onMouseUp={ event => {
          this.handleStopPan(event);
          this.handleStopZoom(event)
        } }
        onWheel={ event => {
          this.handlePinch(event)
        }}
        onMouseEnter={event => {
          this.handleUpdateFocus(event, true);
        }}
        onMouseLeave={event => {
          this.handleUpdateFocus(event, false);
        }}
      >

        <rect
          fill={this.props.background}
          x={0}
          y={0}
          width={this.props.width}
          height={this.props.height}
          style={{pointerEvents: "none"}}
        />

        <g
          ref="originalSVG"
          transform={matrixStr}
          style={gStyle}
          onMouseDown={ event => this.handleEvent(event)}
          onMouseMove={event => this.handleEvent(event)}
          onMouseUp={event => this.handleEvent(event)}
          onClick={event => this.handleEvent(event)}
        >
          <rect
            fill={this.props.SVGBackground}
            x={0}
            y={0}
            width={originalSVG.props.width}
            height={originalSVG.props.height}/>
          <g ref="content">
            {originalSVG.props.children}
          </g>
        </g>

        {focus && (tool === TOOL_NONE) && detectAutoPan ?
          <g style={{pointerEvents: "none"}}>
            <Gradient direction={autoPanX} SVGWidth={SVGWidth} SVGHeight={SVGHeight}/>
            <Gradient direction={autoPanY} SVGWidth={SVGWidth} SVGHeight={SVGHeight}/>
          </g> : null}

        {zoomSelectionRect}
      </svg>
    );
  }
}

Viewer.propTypes = {
  //width of the viewer displayed on screen
  width: React.PropTypes.number.isRequired,

  //height of the viewer displayed on screen
  height: React.PropTypes.number.isRequired,

  //background of the viewer
  background: React.PropTypes.string,

  //background of the svg
  SVGBackground: React.PropTypes.string,

  //value of the viewer (current point of view)
  value: React.PropTypes.object.isRequired,

  //CSS style of the SVG tag
  style: React.PropTypes.object,

  //array of keys that in zoom mode switch zoom in and zoom out
  specialKeys: React.PropTypes.arrayOf(React.PropTypes.number),

  //detect zoom operation performed trough pinch gesture or mouse scroll
  detectPinch: React.PropTypes.bool,

  //perform PAN if the mouse is on viewer border
  detectAutoPan: React.PropTypes.bool,

  //handler something changed
  onChange: React.PropTypes.func.isRequired,

  //handler click
  onClick: React.PropTypes.func,

  //handler mouseup
  onMouseUp: React.PropTypes.func,

  //handler mousemove
  onMouseMove: React.PropTypes.func,

  //handler mousedown
  onMouseDown: React.PropTypes.func,

  //current active tool (TOOL_NONE, TOOL_PAN, TOOL_ZOOM)
  tool: React.PropTypes.oneOf([TOOL_NONE, TOOL_PAN, TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT]),

  //accept only one node SVG
  children: function (props, propName, componentName) {
    // Only accept a single child, of the appropriate type
    //credits: http://www.mattzabriskie.com/blog/react-validating-children
    var prop = props[propName];
    var types = ['svg'];
    if (React.Children.count(prop) !== 1 ||
      types.indexOf(prop.type) === -1) {
      return new Error(
        '`' + componentName + '` ' +
        'should have a single child of the following types: ' +
        ' `' + types.join('`, `') + '`.'
      );
    }
    if (!prop.props.hasOwnProperty('width') || !prop.props.hasOwnProperty('height')) {
      return new Error('SVG should have props `width` and `height`');
    }

  }
};

Viewer.defaultProps = {
  style: {},
  background: "#616264",
  SVGBackground: "#fff",
  tool: TOOL_NONE,
  detectPinch: true,
  detectAutoPan: true,
  specialKeys: [91, 17] //91=Win/Cmd 17=Ctrl,
};
<|endoftext|>


<commit_before>var request = require('request').defaults({
        baseUrl: 'https://api.projectoxford.ai/vision/v1/',
        headers: {'User-Agent': 'nodejs/0.3.0'}}),
    fs = require('fs'),
    _Promise = require('bluebird');

const analyzeUrl = '/analyses';
const thumbnailUrl = '/thumbnails';
const ocrUrl = '/ocr';

/**
 * @namespace
 * @memberof Client
 */
var vision = function (key) {
    /**
     * @private
     */
    function _return(error, response, resolve, reject) {
        if (error) {
            return reject(error);
        }

        if (response.statusCode !== 200) {
            reject(response.body);
        }

        return resolve(response.body);
    }

    /**
     * (Private) Analyze a local image, using a fs pipe
     * @private
     * @param  {string} image       - Path to image
     * @param  {Object} options     - Options object
     * @return {Promise}            - Promise resolving with the resulting JSON
     */
    function _analyzeLocal(image, options) {
        return new _Promise(function (resolve, reject) {
            fs.createReadStream(image).pipe(request.post({
                uri: analyzeUrl,
                headers: {
                    'Ocp-Apim-Subscription-Key': key,
                    'Content-Type': 'application/octet-stream'
                },
                qs: options
            }, (error, response) => {
                response.body = JSON.parse(response.body);
                _return(error, response, resolve, reject);
            }));
        });
    }

    /**
     * (Private) Analyze an online image
     * @private
     * @param  {string} image       - Url to image
     * @param  {Object} options     - Options object
     * @return {Promise}            - Promise resolving with the resulting JSON
     */
    function _analyzeOnline(image, options) {
        return new _Promise(function (resolve, reject) {
            request.post({
                uri: analyzeUrl,
                headers: {'Ocp-Apim-Subscription-Key': key},
                json: true,
                body: {url: image},
                qs: options
            }, (error, response) => _return(error, response, resolve, reject));
        });
    }

    /**
     * This operation does a deep analysis on the given image and then extracts a
     * set of rich visual features based on the image content.
     *
     * @param  {Object}  options                - Options object describing features to extract
     * @param  {string}  options.url            - Url to image to be analyzed
     * @param  {string}  options.path           - Path to image to be analyzed
     * @param  {boolean} options.ImageType      - Detects if image is clipart or a line drawing.
     * @param  {boolean} options.Color          - Determines the accent color, dominant color, if image is black&white.
     * @param  {boolean} options.Faces          - Detects if faces are present. If present, generate coordinates, gender and age.
     * @param  {boolean} options.Adult          - Detects if image is pornographic in nature (nudity or sex act). Sexually suggestive content is also detected.
     * @param  {boolean} options.Categories     - Image categorization; taxonomy defined in documentation.
     * @param  {boolean} options.Tags           - Tags the image with a detailed list of words related to the image content.
     * @param  {boolean} options.Description    - Describes the image content with a complete English sentence.
     * @return {Promise}                        - Promise resolving with the resulting JSON
     */
    function analyzeImage(options) {
        let test = /(ImageType)|(Color)|(Faces)|(Adult)|(Categories)|(Tags)|(Description)/;
        let query = [];

        for (var key in options) {
            if (test.test(key)&& options[key]) {
                query.push(key);
            }
        };

        let qs = {visualFeatures: query.join()};

        if (options.path) {
            return _analyzeLocal(options.path, qs);
        }
        if (options.url) {
            return _analyzeOnline(options.url, qs);
        }
    }

    /**
     * (Private) Get a thumbnail for a local image, using a fs pipe
     * @private
     * @param  {string} image       - Path to image
     * @param  {Object} options     - Options object
     * @return {Promise}            - Promise resolving with the resulting JSON
     */
    function _thumbnailLocal(image, options, pipe) {
        return new _Promise(function (resolve, reject) {
            fs.createReadStream(image).pipe(request.post({
                uri: thumbnailUrl,
                headers: {
                    'Ocp-Apim-Subscription-Key': key,
                    'Content-Type': 'application/octet-stream'
                },
                qs: options
            }, (error, response) => {
                _return(error, response, resolve, reject);
            })).pipe(pipe);
        });
    }

    /**
     * (Private) Get a thumbnail for am online image
     * @private
     * @param  {string} image       - url to image
     * @param  {Object} options     - Options object
     * @return {Promise}            - Promise resolving with the resulting JSON
     */
    function _thumbnailOnline(image, options, pipe) {
        return new _Promise(function (resolve, reject) {
            request.post({
                uri: thumbnailUrl,
                headers: {'Ocp-Apim-Subscription-Key': key},
                json: true,
                body: {url: image},
                qs: options
            }, (error, response) => _return(error, response, resolve, reject))
            .pipe(pipe);
        });
    }

    /**
     * Generate a thumbnail image to the user-specified width and height. By default, the
     * service analyzes the image, identifies the region of interest (ROI), and generates
     * smart crop coordinates based on the ROI. Smart cropping is designed to help when you
     * specify an aspect ratio that differs from the input image.
     *
     * @param  {Object}  options                - Options object describing features to extract
     * @param  {string}  options.url            - Url to image to be thumbnailed
     * @param  {string}  options.path           - Path to image to be thumbnailed
     * @param  {number}  options.width          - Width of the thumb in pixels
     * @param  {number}  options.height         - Height of the thumb in pixels
     * @param  {boolean} options.smartCropping  - Should SmartCropping be enabled?
     * @param  {Object}  options.pipe           - We'll pipe the returned image to this object
     * @return {Promise}                        - Promise resolving with the resulting JSON
     */
    function thumbnail(options) {
        let qs = {
            width: (options.width) ? options.width : 50,
            height: (options.height) ? options.height : 50,
            smartCropping: (options.smartCropping) ? options.smartCropping : false
        };

        if (options.path) {
            return _thumbnailLocal(options.path, qs, options.pipe);
        }
        if (options.url) {
            return _thumbnailOnline(options.url, qs, options.pipe);
        }
    }

    /**
     * (Private) OCR a local image, using a fs pipe
     * @private
     * @param  {string} image       - Path to image
     * @param  {Object} options     - Options object
     * @return {Promise}            - Promise resolving with the resulting JSON
     */
    function _ocrLocal(image, options) {
        return new _Promise(function (resolve, reject) {
            fs.createReadStream(image).pipe(request.post({
                uri: ocrUrl,
                headers: {
                    'Ocp-Apim-Subscription-Key': key,
                    'Content-Type': 'application/octet-stream'
                },
                qs: options
            }, (error, response) => {
                response.body = JSON.parse(response.body);
                _return(error, response, resolve, reject);
            }));
        });
    }

    /**
     * (Private) OCR an online image
     * @private
     * @param  {string} image       - url to image
     * @param  {Object} options     - Options object
     * @return {Promise}            - Promise resolving with the resulting JSON
     */
    function _ocrOnline(image, options) {
        return new _Promise(function (resolve, reject) {
            request.post({
                uri: ocrUrl,
                headers: {'Ocp-Apim-Subscription-Key': key},
                json: true,
                body: {url: image},
                qs: options
            }, (error, response) => _return(error, response, resolve, reject));
        });
    }

    /**
     * Optical Character Recognition (OCR) detects text in an image and extracts the recognized
     * characters into a machine-usable character stream.
     *
     * @param  {Object}  options                    - Options object describing features to extract
     * @param  {string}  options.url                - Url to image to be analyzed
     * @param  {string}  options.path               - Path to image to be analyzed
     * @param  {string}  options.language           - BCP-47 language code of the text to be detected in the image. Default value is "unk", then the service will auto detect the language of the text in the image.
     * @param  {string}  options.detectOrientation  - Detect orientation of text in the image
     * @return {Promise}                            - Promise resolving with the resulting JSON
     */
    function ocr(options) {
        let qs = {
            language: (options.language) ? options.language : 'unk',
            detectOrientation: (options.detectOrientation) ? options.detectOrientation : true
        };

        if (options.path) {
            return _ocrLocal(options.path, qs);
        }
        if (options.url) {
            return _ocrOnline(options.url, qs);
        }
    }

    return {
        analyzeImage: analyzeImage,
        thumbnail: thumbnail,
        ocr: ocr
    };
};

module.exports = vision;
<commit_msg>Fix spacing issue<commit_after>var request = require('request').defaults({
        baseUrl: 'https://api.projectoxford.ai/vision/v1/',
        headers: {'User-Agent': 'nodejs/0.3.0'}}),
    fs = require('fs'),
    _Promise = require('bluebird');

const analyzeUrl = '/analyses';
const thumbnailUrl = '/thumbnails';
const ocrUrl = '/ocr';

/**
 * @namespace
 * @memberof Client
 */
var vision = function (key) {
    /**
     * @private
     */
    function _return(error, response, resolve, reject) {
        if (error) {
            return reject(error);
        }

        if (response.statusCode !== 200) {
            reject(response.body);
        }

        return resolve(response.body);
    }

    /**
     * (Private) Analyze a local image, using a fs pipe
     * @private
     * @param  {string} image       - Path to image
     * @param  {Object} options     - Options object
     * @return {Promise}            - Promise resolving with the resulting JSON
     */
    function _analyzeLocal(image, options) {
        return new _Promise(function (resolve, reject) {
            fs.createReadStream(image).pipe(request.post({
                uri: analyzeUrl,
                headers: {
                    'Ocp-Apim-Subscription-Key': key,
                    'Content-Type': 'application/octet-stream'
                },
                qs: options
            }, (error, response) => {
                response.body = JSON.parse(response.body);
                _return(error, response, resolve, reject);
            }));
        });
    }

    /**
     * (Private) Analyze an online image
     * @private
     * @param  {string} image       - Url to image
     * @param  {Object} options     - Options object
     * @return {Promise}            - Promise resolving with the resulting JSON
     */
    function _analyzeOnline(image, options) {
        return new _Promise(function (resolve, reject) {
            request.post({
                uri: analyzeUrl,
                headers: {'Ocp-Apim-Subscription-Key': key},
                json: true,
                body: {url: image},
                qs: options
            }, (error, response) => _return(error, response, resolve, reject));
        });
    }

    /**
     * This operation does a deep analysis on the given image and then extracts a
     * set of rich visual features based on the image content.
     *
     * @param  {Object}  options                - Options object describing features to extract
     * @param  {string}  options.url            - Url to image to be analyzed
     * @param  {string}  options.path           - Path to image to be analyzed
     * @param  {boolean} options.ImageType      - Detects if image is clipart or a line drawing.
     * @param  {boolean} options.Color          - Determines the accent color, dominant color, if image is black&white.
     * @param  {boolean} options.Faces          - Detects if faces are present. If present, generate coordinates, gender and age.
     * @param  {boolean} options.Adult          - Detects if image is pornographic in nature (nudity or sex act). Sexually suggestive content is also detected.
     * @param  {boolean} options.Categories     - Image categorization; taxonomy defined in documentation.
     * @param  {boolean} options.Tags           - Tags the image with a detailed list of words related to the image content.
     * @param  {boolean} options.Description    - Describes the image content with a complete English sentence.
     * @return {Promise}                        - Promise resolving with the resulting JSON
     */
    function analyzeImage(options) {
        let test = /(ImageType)|(Color)|(Faces)|(Adult)|(Categories)|(Tags)|(Description)/;
        let query = [];

        for (var key in options) {
            if (test.test(key) && options[key]) {
                query.push(key);
            }
        };

        let qs = {visualFeatures: query.join()};

        if (options.path) {
            return _analyzeLocal(options.path, qs);
        }
        if (options.url) {
            return _analyzeOnline(options.url, qs);
        }
    }

    /**
     * (Private) Get a thumbnail for a local image, using a fs pipe
     * @private
     * @param  {string} image       - Path to image
     * @param  {Object} options     - Options object
     * @return {Promise}            - Promise resolving with the resulting JSON
     */
    function _thumbnailLocal(image, options, pipe) {
        return new _Promise(function (resolve, reject) {
            fs.createReadStream(image).pipe(request.post({
                uri: thumbnailUrl,
                headers: {
                    'Ocp-Apim-Subscription-Key': key,
                    'Content-Type': 'application/octet-stream'
                },
                qs: options
            }, (error, response) => {
                _return(error, response, resolve, reject);
            })).pipe(pipe);
        });
    }

    /**
     * (Private) Get a thumbnail for am online image
     * @private
     * @param  {string} image       - url to image
     * @param  {Object} options     - Options object
     * @return {Promise}            - Promise resolving with the resulting JSON
     */
    function _thumbnailOnline(image, options, pipe) {
        return new _Promise(function (resolve, reject) {
            request.post({
                uri: thumbnailUrl,
                headers: {'Ocp-Apim-Subscription-Key': key},
                json: true,
                body: {url: image},
                qs: options
            }, (error, response) => _return(error, response, resolve, reject))
            .pipe(pipe);
        });
    }

    /**
     * Generate a thumbnail image to the user-specified width and height. By default, the
     * service analyzes the image, identifies the region of interest (ROI), and generates
     * smart crop coordinates based on the ROI. Smart cropping is designed to help when you
     * specify an aspect ratio that differs from the input image.
     *
     * @param  {Object}  options                - Options object describing features to extract
     * @param  {string}  options.url            - Url to image to be thumbnailed
     * @param  {string}  options.path           - Path to image to be thumbnailed
     * @param  {number}  options.width          - Width of the thumb in pixels
     * @param  {number}  options.height         - Height of the thumb in pixels
     * @param  {boolean} options.smartCropping  - Should SmartCropping be enabled?
     * @param  {Object}  options.pipe           - We'll pipe the returned image to this object
     * @return {Promise}                        - Promise resolving with the resulting JSON
     */
    function thumbnail(options) {
        let qs = {
            width: (options.width) ? options.width : 50,
            height: (options.height) ? options.height : 50,
            smartCropping: (options.smartCropping) ? options.smartCropping : false
        };

        if (options.path) {
            return _thumbnailLocal(options.path, qs, options.pipe);
        }
        if (options.url) {
            return _thumbnailOnline(options.url, qs, options.pipe);
        }
    }

    /**
     * (Private) OCR a local image, using a fs pipe
     * @private
     * @param  {string} image       - Path to image
     * @param  {Object} options     - Options object
     * @return {Promise}            - Promise resolving with the resulting JSON
     */
    function _ocrLocal(image, options) {
        return new _Promise(function (resolve, reject) {
            fs.createReadStream(image).pipe(request.post({
                uri: ocrUrl,
                headers: {
                    'Ocp-Apim-Subscription-Key': key,
                    'Content-Type': 'application/octet-stream'
                },
                qs: options
            }, (error, response) => {
                response.body = JSON.parse(response.body);
                _return(error, response, resolve, reject);
            }));
        });
    }

    /**
     * (Private) OCR an online image
     * @private
     * @param  {string} image       - url to image
     * @param  {Object} options     - Options object
     * @return {Promise}            - Promise resolving with the resulting JSON
     */
    function _ocrOnline(image, options) {
        return new _Promise(function (resolve, reject) {
            request.post({
                uri: ocrUrl,
                headers: {'Ocp-Apim-Subscription-Key': key},
                json: true,
                body: {url: image},
                qs: options
            }, (error, response) => _return(error, response, resolve, reject));
        });
    }

    /**
     * Optical Character Recognition (OCR) detects text in an image and extracts the recognized
     * characters into a machine-usable character stream.
     *
     * @param  {Object}  options                    - Options object describing features to extract
     * @param  {string}  options.url                - Url to image to be analyzed
     * @param  {string}  options.path               - Path to image to be analyzed
     * @param  {string}  options.language           - BCP-47 language code of the text to be detected in the image. Default value is "unk", then the service will auto detect the language of the text in the image.
     * @param  {string}  options.detectOrientation  - Detect orientation of text in the image
     * @return {Promise}                            - Promise resolving with the resulting JSON
     */
    function ocr(options) {
        let qs = {
            language: (options.language) ? options.language : 'unk',
            detectOrientation: (options.detectOrientation) ? options.detectOrientation : true
        };

        if (options.path) {
            return _ocrLocal(options.path, qs);
        }
        if (options.url) {
            return _ocrOnline(options.url, qs);
        }
    }

    return {
        analyzeImage: analyzeImage,
        thumbnail: thumbnail,
        ocr: ocr
    };
};

module.exports = vision;
<|endoftext|>


<commit_before>import Immutable from 'immutable'
import debug from 'debug'
import parse from './parse'
import * as error from './error'

var Iterable = Immutable.Iterable

debug = debug('immpatch')

var operators = {

  add (op) {
    if (typeof op.value === 'undefined') {
      throw new error.InvalidOperationError(
        'add',
        'Operation object must have a "value" member'
      )
    }
    var container, idx
    var path = op.path
    var value = op.value
    var parent = path.slice(0, -1)
    //
    // If the target location specifies an array index,
    // a new value is inserted into the array at the
    // specified index.
    //
    // The target location must reference an element
    // to an existing array.
    //
    if (this.hasIn(parent)) {
      // check the parent container and see if it is
      // an indexed collection (array in immutable.js)
      parent = path.slice(0, -1)
      container = this.getIn(parent)
      if (Iterable.isIndexed(container)) {
        // only continue if the index is within the
        // array bounds, or it is '-', which
        // indicates that the value is to be appended
        idx = path[path.length - 1]
        if (idx === '-') {
          return this.updateIn(parent, (coll) => {
            return coll.push(value)
          })
        }
        if (idx >= 0 && idx <= container.size) {
          debug('add array element')
          return this.updateIn(parent, (coll) => {
            return coll.splice(idx, 0, value)
          })
        }
      }
    }
    //
    // The parent container does not exist, or, it
    // is not an array. Continue only if the target
    // location is:
    //
    // 1. The root of the target document, or;
    // 2. A member to add to an existing object
    //
    // (1) is root doc    or (2) container is an object
    if (path.length === 0 || Iterable.isKeyed(container)) {
      debug('add object member')
      return this.setIn(path, value)
    }
    // otherwise, it must be considered an error
    throw new error.InvalidOperationError(
      'add',
      'Target location must reference the root document or an existing object'
    )
  },

  remove (op) {
    if (!this.hasIn(op.path)) {
      throw new error.PathNotFoundError(op.path)
    }
    return this.removeIn(op.path)
  },

  replace (op) {
    if (typeof op.value === 'undefined') {
      throw new error.InvalidOperationError(
        'replace',
        'Operation object must have a "value" member'
      )
    }
    if (!this.hasIn(op.path)) {
      throw new error.PathNotFoundError(op.path)
    }
    return this.setIn(op.path, op.value)
  },

  move (op) {
    if (op.from == null) {
      throw new error.InvalidOperationError(
        'move',
        'Operation object must have a "from" member'
      )
    }
    if (!this.hasIn(op.from)) {
      throw new error.PathNotFoundError(op.from)
    }
    var from = op.from
    var path = op.path
    var value = this.getIn(from)
    var target = this.getIn(path)
    if (Iterable.isIterable(target) && target.isSubset(value)) {
      throw new error.InvalidOperationError(
        'move',
        '"from" location cannot be moved into it\'s child'
      )
    }
    // this operation is functionally identical to a "remove"
    // operation on the "from" location, followed immediately
    // by an "add" operation at the target location with the
    // value that was just removed
    var self = operators.remove.call(this, { path: from })
    return operators.add.call(self, { path, value })
  },

  copy (op) {
    if (op.from == null) {
      throw new error.InvalidOperationError(
        'copy',
        'Operation object must have a "from" member'
      )
    }
    if (!this.hasIn(op.from)) {
      throw new error.PathNotFoundError(op.from)
    }
    var from = op.from
    var path = op.path
    // This operation is functionally identical to an "add"
    // operation at the target location using the value
    // specified in the "from" member
    var value = this.getIn(from)
    return operators.add.call(this, { path, value })
  },

  test (op) {
    if (typeof op.value === 'undefined') {
      throw new error.InvalidOperationError(
        'test',
        'Operation object must have a "value" member'
      )
    }
    var path = op.path
    var value = op.value
    var target = this.getIn(path)
    // Use Immutable.is for testing, as what the specification
    // describes is essentially a recursive/deep equality check
    if (!Immutable.is(target, Immutable.fromJS(value))) {
      throw new error.TestFailError(path, value, target)
    }
    return this
  }

}

/**
 * Update the immutable object using JSON Patch
 * operations.
 *
 * @param {object} object
 * @param {array|object} ops
 * @returns {object}
 */

export default function patch (object, ops) {
  ops = Array.isArray(ops) ? ops : [ops]
  object = ops.reduce((ob, op) => {
    op = parse(op, ob)
    return operators[op.op].call(ob, op)
  }, object)
  // Re-convert the returned object into an
  // immutable structure, in case the entire
  // object was replaced with a POJO
  return Immutable.fromJS(object)
}
<commit_msg>Changed to not reassign imported identifier<commit_after>import Immutable from 'immutable'
import makeDebug from 'debug'
import parse from './parse'
import * as error from './error'

var Iterable = Immutable.Iterable

const debug = makeDebug('immpatch')

var operators = {

  add (op) {
    if (typeof op.value === 'undefined') {
      throw new error.InvalidOperationError(
        'add',
        'Operation object must have a "value" member'
      )
    }
    var container, idx
    var path = op.path
    var value = op.value
    var parent = path.slice(0, -1)
    //
    // If the target location specifies an array index,
    // a new value is inserted into the array at the
    // specified index.
    //
    // The target location must reference an element
    // to an existing array.
    //
    if (this.hasIn(parent)) {
      // check the parent container and see if it is
      // an indexed collection (array in immutable.js)
      parent = path.slice(0, -1)
      container = this.getIn(parent)
      if (Iterable.isIndexed(container)) {
        // only continue if the index is within the
        // array bounds, or it is '-', which
        // indicates that the value is to be appended
        idx = path[path.length - 1]
        if (idx === '-') {
          return this.updateIn(parent, (coll) => {
            return coll.push(value)
          })
        }
        if (idx >= 0 && idx <= container.size) {
          debug('add array element')
          return this.updateIn(parent, (coll) => {
            return coll.splice(idx, 0, value)
          })
        }
      }
    }
    //
    // The parent container does not exist, or, it
    // is not an array. Continue only if the target
    // location is:
    //
    // 1. The root of the target document, or;
    // 2. A member to add to an existing object
    //
    // (1) is root doc    or (2) container is an object
    if (path.length === 0 || Iterable.isKeyed(container)) {
      debug('add object member')
      return this.setIn(path, value)
    }
    // otherwise, it must be considered an error
    throw new error.InvalidOperationError(
      'add',
      'Target location must reference the root document or an existing object'
    )
  },

  remove (op) {
    if (!this.hasIn(op.path)) {
      throw new error.PathNotFoundError(op.path)
    }
    return this.removeIn(op.path)
  },

  replace (op) {
    if (typeof op.value === 'undefined') {
      throw new error.InvalidOperationError(
        'replace',
        'Operation object must have a "value" member'
      )
    }
    if (!this.hasIn(op.path)) {
      throw new error.PathNotFoundError(op.path)
    }
    return this.setIn(op.path, op.value)
  },

  move (op) {
    if (op.from == null) {
      throw new error.InvalidOperationError(
        'move',
        'Operation object must have a "from" member'
      )
    }
    if (!this.hasIn(op.from)) {
      throw new error.PathNotFoundError(op.from)
    }
    var from = op.from
    var path = op.path
    var value = this.getIn(from)
    var target = this.getIn(path)
    if (Iterable.isIterable(target) && target.isSubset(value)) {
      throw new error.InvalidOperationError(
        'move',
        '"from" location cannot be moved into it\'s child'
      )
    }
    // this operation is functionally identical to a "remove"
    // operation on the "from" location, followed immediately
    // by an "add" operation at the target location with the
    // value that was just removed
    var self = operators.remove.call(this, { path: from })
    return operators.add.call(self, { path, value })
  },

  copy (op) {
    if (op.from == null) {
      throw new error.InvalidOperationError(
        'copy',
        'Operation object must have a "from" member'
      )
    }
    if (!this.hasIn(op.from)) {
      throw new error.PathNotFoundError(op.from)
    }
    var from = op.from
    var path = op.path
    // This operation is functionally identical to an "add"
    // operation at the target location using the value
    // specified in the "from" member
    var value = this.getIn(from)
    return operators.add.call(this, { path, value })
  },

  test (op) {
    if (typeof op.value === 'undefined') {
      throw new error.InvalidOperationError(
        'test',
        'Operation object must have a "value" member'
      )
    }
    var path = op.path
    var value = op.value
    var target = this.getIn(path)
    // Use Immutable.is for testing, as what the specification
    // describes is essentially a recursive/deep equality check
    if (!Immutable.is(target, Immutable.fromJS(value))) {
      throw new error.TestFailError(path, value, target)
    }
    return this
  }

}

/**
 * Update the immutable object using JSON Patch
 * operations.
 *
 * @param {object} object
 * @param {array|object} ops
 * @returns {object}
 */

export default function patch (object, ops) {
  ops = Array.isArray(ops) ? ops : [ops]
  object = ops.reduce((ob, op) => {
    op = parse(op, ob)
    return operators[op.op].call(ob, op)
  }, object)
  // Re-convert the returned object into an
  // immutable structure, in case the entire
  // object was replaced with a POJO
  return Immutable.fromJS(object)
}
<|endoftext|>


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

import {
    firstChild,
    parentNode
} from './traversal';
import { TreeWalker } from './tree_walker';
import {
    enterContext,
    restoreContext
} from './context';
import { clearUnvisitedDOM } from './alignment';
import { notifications } from './notifications';
import { assertNoUnclosedTags } from './assertions';

/**
 * Patches the document starting at el with the provided function. This function
 * may be called during an existing patch operation.
 * @param {!Element|!DocumentFragment} node The Element or Document
 *     to patch.
 * @param {!function(T)} fn A function containing elementOpen/elementClose/etc.
 *     calls that describe the DOM.
 * @param {T=} data An argument passed to fn to represent DOM state.
 * @template T
 */
var patch = function(node, fn, data) {
  var context = enterContext(node);

  firstChild();
  fn(data);
  parentNode();
  clearUnvisitedDOM(node);

  if (process.env.NODE_ENV !== 'production') {
    assertNoUnclosedTags(context.walker.currentNode, node);
  }

  context.notifyChanges();
  restoreContext();
};

/** */
export {
  patch
};
<commit_msg>Prevent assertions from corrupting state<commit_after>/**
 * Copyright 2015 The Incremental DOM Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
    firstChild,
    parentNode
} from './traversal';
import { TreeWalker } from './tree_walker';
import {
    enterContext,
    restoreContext
} from './context';
import { clearUnvisitedDOM } from './alignment';
import { notifications } from './notifications';
import {
  assertNoUnclosedTags,
  setInAttributes
} from './assertions';

/**
 * Patches the document starting at el with the provided function. This function
 * may be called during an existing patch operation.
 * @param {!Element|!DocumentFragment} node The Element or Document
 *     to patch.
 * @param {!function(T)} fn A function containing elementOpen/elementClose/etc.
 *     calls that describe the DOM.
 * @param {T=} data An argument passed to fn to represent DOM state.
 * @template T
 */
var patch = function(node, fn, data) {
  var context = enterContext(node);
  if (process.env.NODE_ENV !== 'production') {
    setInAttributes(false);
  }

  firstChild();
  fn(data);
  parentNode();
  clearUnvisitedDOM(node);

  if (process.env.NODE_ENV !== 'production') {
    assertNoUnclosedTags(context.walker.currentNode, node);
  }

  context.notifyChanges();
  restoreContext();
};

/** */
export {
  patch
};
<|endoftext|>


<commit_before>module.exports = {
    'import/default': 'error',
    'import/export': 'error',
    'import/extensions': 'error',
    'import/first': 'error',
    'import/named': 'error',
    'import/namespace': 'error',
    'import/newline-after-import': 'error',
    'import/no-absolute-path': 'error',
    'import/no-amd': 'error',
    'import/no-deprecated': 'error',
    'import/no-duplicates': 'error',
    'import/no-dynamic-require': 'error',
    'import/no-extraneous-dependencies': 'error',
    'import/no-mutable-exports': 'error',
    'import/no-named-as-default': 'error',
    'import/no-named-as-default-member': 'error',
    'import/no-unresolved': 'error',
    'import/no-webpack-loader-syntax': 'error',
    'import/order': [
        'error',
        {
            'newlines-between': 'always'
        }
    ]
};
<commit_msg>Changes `import/namespace` to allow computed values. (#30)<commit_after>module.exports = {
    'import/default': 'error',
    'import/export': 'error',
    'import/extensions': 'error',
    'import/first': 'error',
    'import/named': 'error',
    'import/namespace': [
        'error',
        {
            allowComputed: true
        }
    ],
    'import/newline-after-import': 'error',
    'import/no-absolute-path': 'error',
    'import/no-amd': 'error',
    'import/no-deprecated': 'error',
    'import/no-duplicates': 'error',
    'import/no-dynamic-require': 'error',
    'import/no-extraneous-dependencies': 'error',
    'import/no-mutable-exports': 'error',
    'import/no-named-as-default': 'error',
    'import/no-named-as-default-member': 'error',
    'import/no-unresolved': 'error',
    'import/no-webpack-loader-syntax': 'error',
    'import/order': [
        'error',
        {
            'newlines-between': 'always'
        }
    ]
};
<|endoftext|>