<commit_before>import merge from 'lodash/merge';
import range from 'lodash/range';
import { serializeQueryParameters } from './utils';

export default function(worker) {
  var socketInstance;

  var currentUrl;

  const strongSocketDefaults = {
    params: {
      sri: 'overrideMe',
      mobile: 1
    },
    options: {
      name: 'unnamed',
      pingMaxLag: 7000, // time to wait for pong before reseting the connection
      pingDelay: 1000, // time between pong and ping
      autoReconnectDelay: 1000,
      ignoreUnknownMessages: true,
      sendOnOpen: null, // message to send on socket open
      registeredEvents: []
    }
  };

  function StrongSocket(clientId, socketEndPoint, url, version, settings) {
    this.settings = merge({}, strongSocketDefaults, settings);
    this.settings.params.sri = clientId;
    this.socketEndPoint = socketEndPoint;
    this.url = url;
    this.version = version;
    this.options = this.settings.options;
    this.ws = null;
    this.pingSchedule = null;
    this.connectSchedule = null;
    this.ackableMessages = [];
    this.lastPingTime = this.now();
    this.currentLag = 0;
    this.averageLag = 0;
    this.autoReconnect = true;
    this.tryAnotherUrl = false;
    this.urlsPool = [this.socketEndPoint].concat(range(9021, 9030).map(
      e => this.socketEndPoint + ':' + e
    ));

    this.debug('Debug is enabled');
    this.connect();
  }

  StrongSocket.prototype = {

    connect: function() {
      var self = this;
      self.destroy();
      self.autoReconnect = true;
      var fullUrl = 'ws://' + self.baseUrl() + self.url + '?' + serializeQueryParameters(self.settings.params);
      self.debug('connection attempt to ' + fullUrl, true);
      try {
        if (WebSocket) self.ws = new WebSocket(fullUrl);
        else throw '[lila] no websockets available!';

        self.ws.onerror = function(e) {
          self.onError(e);
        };
        self.ws.onclose = function() {
          postMessage({ topic: 'disconnected' });
          if (self.autoReconnect) {
            self.debug('Will autoreconnect in ' + self.options.autoReconnectDelay);
            self.scheduleConnect(self.options.autoReconnectDelay);
          }
        };
        self.ws.onopen = function() {
          self.debug('connected to ' + fullUrl, true);
          postMessage({ topic: 'onOpen' });
          if (self.options.sendOnOpen) self.send(self.options.sendOnOpen);
          self.onSuccess();
          self.pingNow();
          var resend = self.ackableMessages;
          self.ackableMessages = [];
          resend.forEach(function(x) { self.send(x.t, x.d); });
        };
        self.ws.onmessage = function(e) {
          var msg = JSON.parse(e.data);
          var mData = msg.d || [];

          if (msg.t === 'n') self.pong();
          else self.debug(e.data);

          if (msg.t === 'b') mData.forEach(function(x) { self.handle(x); });
          else self.handle(msg);
        };
      } catch (e) {
        self.onError(e);
      }
      self.scheduleConnect(self.options.pingMaxLag);
    },

    setVersion: function(version) {
      this.version = version;
      this.connect();
    },

    send: function(t, d, o) {
      var self = this;
      var data = d || {},
      options = o || {};
      if (options.ackable)
        self.ackableMessages.push({
          t: t,
          d: d
        });
      var message = JSON.stringify({
        t: t,
        d: data
      });
      self.debug('send ' + message);
      try {
        self.ws.send(message);
      } catch (e) {
        self.debug(e);
      }
    },

    sendAckable: function(t, d) {
      this.send(t, d, { ackable: true });
    },

    scheduleConnect: function(delay) {
      var self = this;
      // self.debug('schedule connect ' + delay);
      clearTimeout(self.pingSchedule);
      clearTimeout(self.connectSchedule);
      self.connectSchedule = setTimeout(function() {
        self.tryAnotherUrl = true;
        self.connect();
      }, delay);
    },

    schedulePing: function(delay) {
      var self = this;
      clearTimeout(self.pingSchedule);
      self.pingSchedule = setTimeout(function() {
        self.pingNow();
      }, delay);
    },

    pingNow: function() {
      var self = this;
      clearTimeout(self.pingSchedule);
      clearTimeout(self.connectSchedule);
      try {
        self.ws.send(self.pingData());
        self.lastPingTime = self.now();
      } catch (e) {
        self.debug(e, true);
      }
      self.scheduleConnect(self.options.pingMaxLag);
    },

    pong: function() {
      var self = this;
      clearTimeout(self.connectSchedule);
      self.schedulePing(self.options.pingDelay);
      self.currentLag = self.now() - self.lastPingTime;
      if (!self.averageLag) self.averageLag = self.currentLag;
      else self.averageLag = 0.2 * (self.currentLag - self.averageLag) + self.averageLag;
    },

    pingData: function() {
      return JSON.stringify({
        t: 'p',
        v: this.version
      });
    },

    handle: function(msg) {
      var self = this;
      if (msg.v) {
        if (msg.v <= self.version) {
          self.debug('already has event ' + msg.v);
          return;
        }
        if (msg.v > self.version + 1) {
          self.debug('event gap detected from ' + self.version + ' to ' + msg.v);
        }
        self.version = msg.v;
      }
      switch (msg.t || false) {
        case false:
          break;
        case 'ack':
          self.ackableMessages = [];
          break;
        default:
          if (self.options.registeredEvents.indexOf(msg.t) !== -1) {
            postMessage({ topic: 'handle', payload: msg });
          }
      }
    },

    now: function() {
      return new Date().getTime();
    },

    debug: function(msg, always) {
      if ((always || this.options.debug) && console && console.debug) {
        console.debug('[' + this.options.name + ' ' + this.settings.params.sri + ']', msg);
      }
    },

    destroy: function() {
      clearTimeout(this.pingSchedule);
      clearTimeout(this.connectSchedule);
      this.disconnect();
      this.ws = null;
    },

    disconnect: function() {
      if (this.ws) {
        this.debug('Disconnect', true);
        this.autoReconnect = false;
        this.ws.onerror = function() {};
        this.ws.onclose = function() {};
        this.ws.onopen = function() {};
        this.ws.onmessage = function() {};
        this.ws.close();
      }
    },

    onError: function(e) {
      var self = this;
      postMessage({ topic: 'onError' });
      self.options.debug = true;
      self.debug('error: ' + JSON.stringify(e));
      self.tryAnotherUrl = true;
      postMessage({ topic: 'disconnected' });
      clearTimeout(self.pingSchedule);
    },

    onSuccess: function() {
      postMessage({ topic: 'connected' });
    },

    pingInterval: function() {
      return this.options.pingDelay + this.averageLag;
    },

    baseUrl: function() {
      if (this.socketEndPoint === 'socket.en.lichess.org') {
        if (!currentUrl) {
          currentUrl = this.urlsPool[0];
        } else if (this.tryAnotherUrl) {
          this.tryAnotherUrl = false;
          currentUrl = this.urlsPool[(this.urlsPool.indexOf(currentUrl) + 1) % this.urlsPool.length];
        }
        return currentUrl;
      }

      return this.socketEndPoint;
    }
  };

  function create(payload) {
    // don't always recreate default socket on page change
    if (socketInstance && socketInstance.options.name === 'default') {
      return;
    }

    if (socketInstance) {
      socketInstance.destroy();
      socketInstance = null;
    }
    socketInstance = new StrongSocket(
      payload.clientId,
      payload.socketEndPoint,
      payload.url,
      payload.version,
      payload.opts
    );
  }

  worker.addEventListener('message', function(msg) {
    switch (msg.data.topic) {
      case 'create':
        create(msg.data.payload);
        break;
      case 'send':
        const [t, d, o] = msg.data.payload;
        if (socketInstance) socketInstance.send(t, d, o);
        break;
      case 'connect':
        if (socketInstance) socketInstance.connect();
        break;
      case 'disconnect':
        if (socketInstance) socketInstance.destroy();
        break;
      case 'destroy':
        if (socketInstance) {
          socketInstance.destroy();
          socketInstance = null;
        }
        break;
      case 'setVersion':
        if (socketInstance) {
          socketInstance.setVersion(msg.data.payload);
        }
        break;
      case 'averageLag':
        if (socketInstance) postMessage({ topic: 'averageLag', payload: socketInstance.averageLag });
        else postMessage({ topic: 'averageLag', payload: null });
        break;
      default:
        throw new Error('socker worker message not supported: ' + msg.data.topic);
    }
  });
}
<commit_msg>Fix socket recreation on page change<commit_after>import merge from 'lodash/merge';
import range from 'lodash/range';
import { serializeQueryParameters } from './utils';

export default function(worker) {
  var socketInstance;

  var currentUrl;

  const strongSocketDefaults = {
    params: {
      sri: 'overrideMe',
      mobile: 1
    },
    options: {
      name: 'unnamed',
      pingMaxLag: 7000, // time to wait for pong before reseting the connection
      pingDelay: 1000, // time between pong and ping
      autoReconnectDelay: 1000,
      ignoreUnknownMessages: true,
      sendOnOpen: null, // message to send on socket open
      registeredEvents: []
    }
  };

  function StrongSocket(clientId, socketEndPoint, url, version, settings) {
    this.settings = merge({}, strongSocketDefaults, settings);
    this.settings.params.sri = clientId;
    this.socketEndPoint = socketEndPoint;
    this.url = url;
    this.version = version;
    this.options = this.settings.options;
    this.ws = null;
    this.pingSchedule = null;
    this.connectSchedule = null;
    this.ackableMessages = [];
    this.lastPingTime = this.now();
    this.currentLag = 0;
    this.averageLag = 0;
    this.autoReconnect = true;
    this.tryAnotherUrl = false;
    this.urlsPool = [this.socketEndPoint].concat(range(9021, 9030).map(
      e => this.socketEndPoint + ':' + e
    ));

    this.debug('Debug is enabled');
    this.connect();
  }

  StrongSocket.prototype = {

    connect: function() {
      var self = this;
      self.destroy();
      self.autoReconnect = true;
      var fullUrl = 'ws://' + self.baseUrl() + self.url + '?' + serializeQueryParameters(self.settings.params);
      self.debug('connection attempt to ' + fullUrl, true);
      try {
        if (WebSocket) self.ws = new WebSocket(fullUrl);
        else throw '[lila] no websockets available!';

        self.ws.onerror = function(e) {
          self.onError(e);
        };
        self.ws.onclose = function() {
          postMessage({ topic: 'disconnected' });
          if (self.autoReconnect) {
            self.debug('Will autoreconnect in ' + self.options.autoReconnectDelay);
            self.scheduleConnect(self.options.autoReconnectDelay);
          }
        };
        self.ws.onopen = function() {
          self.debug('connected to ' + fullUrl, true);
          postMessage({ topic: 'onOpen' });
          if (self.options.sendOnOpen) self.send(self.options.sendOnOpen);
          self.onSuccess();
          self.pingNow();
          var resend = self.ackableMessages;
          self.ackableMessages = [];
          resend.forEach(function(x) { self.send(x.t, x.d); });
        };
        self.ws.onmessage = function(e) {
          var msg = JSON.parse(e.data);
          var mData = msg.d || [];

          if (msg.t === 'n') self.pong();
          else self.debug(e.data);

          if (msg.t === 'b') mData.forEach(function(x) { self.handle(x); });
          else self.handle(msg);
        };
      } catch (e) {
        self.onError(e);
      }
      self.scheduleConnect(self.options.pingMaxLag);
    },

    setVersion: function(version) {
      this.version = version;
      this.connect();
    },

    send: function(t, d, o) {
      var self = this;
      var data = d || {},
      options = o || {};
      if (options.ackable)
        self.ackableMessages.push({
          t: t,
          d: d
        });
      var message = JSON.stringify({
        t: t,
        d: data
      });
      self.debug('send ' + message);
      try {
        self.ws.send(message);
      } catch (e) {
        self.debug(e);
      }
    },

    sendAckable: function(t, d) {
      this.send(t, d, { ackable: true });
    },

    scheduleConnect: function(delay) {
      var self = this;
      // self.debug('schedule connect ' + delay);
      clearTimeout(self.pingSchedule);
      clearTimeout(self.connectSchedule);
      self.connectSchedule = setTimeout(function() {
        self.tryAnotherUrl = true;
        self.connect();
      }, delay);
    },

    schedulePing: function(delay) {
      var self = this;
      clearTimeout(self.pingSchedule);
      self.pingSchedule = setTimeout(function() {
        self.pingNow();
      }, delay);
    },

    pingNow: function() {
      var self = this;
      clearTimeout(self.pingSchedule);
      clearTimeout(self.connectSchedule);
      try {
        self.ws.send(self.pingData());
        self.lastPingTime = self.now();
      } catch (e) {
        self.debug(e, true);
      }
      self.scheduleConnect(self.options.pingMaxLag);
    },

    pong: function() {
      var self = this;
      clearTimeout(self.connectSchedule);
      self.schedulePing(self.options.pingDelay);
      self.currentLag = self.now() - self.lastPingTime;
      if (!self.averageLag) self.averageLag = self.currentLag;
      else self.averageLag = 0.2 * (self.currentLag - self.averageLag) + self.averageLag;
    },

    pingData: function() {
      return JSON.stringify({
        t: 'p',
        v: this.version
      });
    },

    handle: function(msg) {
      var self = this;
      if (msg.v) {
        if (msg.v <= self.version) {
          self.debug('already has event ' + msg.v);
          return;
        }
        if (msg.v > self.version + 1) {
          self.debug('event gap detected from ' + self.version + ' to ' + msg.v);
        }
        self.version = msg.v;
      }
      switch (msg.t || false) {
        case false:
          break;
        case 'ack':
          self.ackableMessages = [];
          break;
        default:
          if (self.options.registeredEvents.indexOf(msg.t) !== -1) {
            postMessage({ topic: 'handle', payload: msg });
          }
      }
    },

    now: function() {
      return new Date().getTime();
    },

    debug: function(msg, always) {
      if ((always || this.options.debug) && console && console.debug) {
        console.debug('[' + this.options.name + ' ' + this.settings.params.sri + ']', msg);
      }
    },

    destroy: function() {
      clearTimeout(this.pingSchedule);
      clearTimeout(this.connectSchedule);
      this.disconnect();
      this.ws = null;
    },

    disconnect: function() {
      if (this.ws) {
        this.debug('Disconnect', true);
        this.autoReconnect = false;
        this.ws.onerror = function() {};
        this.ws.onclose = function() {};
        this.ws.onopen = function() {};
        this.ws.onmessage = function() {};
        this.ws.close();
      }
    },

    onError: function(e) {
      var self = this;
      postMessage({ topic: 'onError' });
      self.options.debug = true;
      self.debug('error: ' + JSON.stringify(e));
      self.tryAnotherUrl = true;
      postMessage({ topic: 'disconnected' });
      clearTimeout(self.pingSchedule);
    },

    onSuccess: function() {
      postMessage({ topic: 'connected' });
    },

    pingInterval: function() {
      return this.options.pingDelay + this.averageLag;
    },

    baseUrl: function() {
      if (this.socketEndPoint === 'socket.en.lichess.org') {
        if (!currentUrl) {
          currentUrl = this.urlsPool[0];
        } else if (this.tryAnotherUrl) {
          this.tryAnotherUrl = false;
          currentUrl = this.urlsPool[(this.urlsPool.indexOf(currentUrl) + 1) % this.urlsPool.length];
        }
        return currentUrl;
      }

      return this.socketEndPoint;
    }
  };

  function create(payload) {
    // don't always recreate default socket on page change
    if (socketInstance && payload.opts.options.name === 'default' &&
      socketInstance.options.name === 'default') {
      return;
    }

    if (socketInstance) {
      socketInstance.destroy();
      socketInstance = null;
    }
    socketInstance = new StrongSocket(
      payload.clientId,
      payload.socketEndPoint,
      payload.url,
      payload.version,
      payload.opts
    );
  }

  worker.addEventListener('message', function(msg) {
    switch (msg.data.topic) {
      case 'create':
        create(msg.data.payload);
        break;
      case 'send':
        var [t, d, o] = msg.data.payload;
        if (socketInstance) socketInstance.send(t, d, o);
        break;
      case 'connect':
        if (socketInstance) socketInstance.connect();
        break;
      case 'disconnect':
        if (socketInstance) socketInstance.destroy();
        break;
      case 'destroy':
        if (socketInstance) {
          socketInstance.destroy();
          socketInstance = null;
        }
        break;
      case 'setVersion':
        if (socketInstance) {
          socketInstance.setVersion(msg.data.payload);
        }
        break;
      case 'averageLag':
        if (socketInstance) postMessage({ topic: 'averageLag', payload: socketInstance.averageLag });
        else postMessage({ topic: 'averageLag', payload: null });
        break;
      default:
        throw new Error('socker worker message not supported: ' + msg.data.topic);
    }
  });
}
<|endoftext|>


<commit_before>/*! jQuery Caret plugin v1.3.0 | (c) 2012, 2013 Andrew C. Dvorak | github.com/acdvorak/jquery.caret */
(function($, undefined) {

    var _input = document.createElement('input');

    var _support = {
        setSelectionRange: ('setSelectionRange' in _input) || ('selectionStart' in _input),
        createTextRange: ('createTextRange' in _input) || ('selection' in document)
    };

    var _rNewlineIE = /\r\n/g,
        _rCarriageReturn = /\r/g;

    var _format = function() {
        var str = arguments[0];
        var args = [].slice.call(arguments, 1);
        return str.replace(/{(\d+)}/g, function(match, number) {
            return typeof(args[number]) !== 'undefined'
                ? args[number]
                : match
                ;
        });
    };

    var _getIndex = function(input, pos) {
        var norm = input.value.replace(_rCarriageReturn, '');
        var len = norm.length;

        if (typeof(pos) === 'undefined') {
            pos = len;
        }

        pos = Math.floor(pos);

        // Negative index counts backward from the end of the input/textarea's value
        if (pos < 0) {
            pos = len + pos;
        }

        return pos;
    };

    var _hasAttr = function(input, attrName) {
        return input.hasAttribute ? input.hasAttribute(attrName) : (typeof(input[attrName]) !== 'undefined');
    };

    /**
     * @class
     * @constructor
     */
    var Range = function(start, end, length, text) {
        this.start = start || 0;
        this.end = end || 0;
        this.length = length || 0;
        this.text = text || '';
    };

    var _getCaretW3 = function(input) {
        return input.selectionStart;
    };

    /**
     * @see http://stackoverflow.com/q/6943000/467582
     */
    var _getCaretIE = function(input) {
        var caret, range, textInputRange, len, endRange;

        // Yeah, you have to focus twice for IE 7 and 8.  *cries*
        input.focus();
        input.focus();

        range = document.selection.createRange();

        if (range && range.parentElement() === input) {
            len = input.value.length;

            // Create a working TextRange that lives only in the input
            textInputRange = input.createTextRange();
            textInputRange.moveToBookmark(range.getBookmark());

            // Check if the start and end of the selection are at the very end
            // of the input, since moveStart/moveEnd doesn't return what we want
            // in those cases
            endRange = input.createTextRange();
            endRange.collapse(false);

            if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
                caret = input.value.replace(_rNewlineIE, '\n').length;
            } else {
                caret = -textInputRange.moveStart("character", -len);
            }

            return caret;
        }

        // TODO: This can occur when you highlight part of a textarea and then click in the middle of the highlighted portion
        alert("Your browser is incredibly stupid.  I don't know what else to say.");

        return 0;
    };

    /**
     * Gets the position of the caret in the given input.
     * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
     * @returns {Number}
     * @see http://stackoverflow.com/questions/263743/how-to-get-cursor-position-in-textarea/263796#263796
     */
    var _getCaret = function(input) {
        if (!input) {
            return undefined;
        }

        // Mozilla, et al.
        if (_support.setSelectionRange) {
            return _getCaretW3(input);
        }
        // IE
        else if (_support.createTextRange) {
            return _getCaretIE(input);
        }

        return undefined;
    };

    var _setCaretW3 = function(input, pos) {
        input.setSelectionRange(pos, pos);
    };

    var _setCaretIE = function(input, pos) {
        var range = input.createTextRange();
        range.move('character', pos);
        range.select();
    };

    /**
     * Sets the position of the caret in the given input.
     * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
     * @param {Number} pos
     * @see http://parentnode.org/javascript/working-with-the-cursor-position/
     */
    var _setCaret = function(input, pos) {
        input.focus();

        // Mozilla, et al.
        if (_support.setSelectionRange) {
            _setCaretW3(input, pos);
        }
        // IE
        else if (_support.createTextRange) {
            _setCaretIE(input, pos);
        }
    };

    /**
     * Inserts the specified text at the current caret position in the given input.
     * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
     * @param {String} text
     * @see http://parentnode.org/javascript/working-with-the-cursor-position/
     */
    var _insertAtCaret = function(input, text) {
        var curPos = _getCaret(input);

        var oldValueNorm = input.value.replace(_rCarriageReturn, '');

        var newLength = +(curPos + text.length + (oldValueNorm.length - curPos));
        var maxLength = +input.getAttribute('maxlength');

        if(_hasAttr(input, 'maxlength') && newLength > maxLength) {
            var delta = text.length - (newLength - maxLength);
            text = text.substr(0, delta);
        }

        input.value = oldValueNorm.substr(0, curPos) + text + oldValueNorm.substr(curPos);

        _setCaret(input, curPos + text.length);
    };

    var _getInputRangeW3 = function(input) {
        var range = new Range();

        range.start = input.selectionStart;
        range.end = input.selectionEnd;

        var min = Math.min(range.start, range.end);
        var max = Math.max(range.start, range.end);

        range.length = max - min;
        range.text = input.value.substring(min, max);

        return range;
    };

    var _getInputRangeIE = function(input) {
        var range = new Range();

        input.focus();

        var selection = document.selection.createRange();

        if (!selection) {
            return range;
        }

        var tr_beginning = input.createTextRange();
        var tr_selection = tr_beginning.duplicate();

        tr_selection.moveToBookmark(selection.getBookmark());
        tr_beginning.setEndPoint('EndToStart', tr_selection);

        range.start = tr_beginning.text.replace(_rNewlineIE, '\n').length;
        range.text = selection.text.replace(_rNewlineIE, '\n').replace(_rCarriageReturn, '\n');

        range.length = range.text.length;
        range.end = range.start + range.length;

        if(range.length === 0) {
            range.start = range.end = _getCaret(input);
        }

        return range;
    };

    /**
     * Gets the selected text range of the given input.
     * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
     * @returns {Range}
     * @see http://stackoverflow.com/a/263796/467582
     * @see http://stackoverflow.com/a/2966703/467582
     */
    var _getInputRange = function(input) {
        if (!input) {
            return undefined;
        }

        // Mozilla, et al.
        if (_support.setSelectionRange) {
            return _getInputRangeW3(input);
        }
        // IE
        else if (_support.createTextRange) {
            return _getInputRangeIE(input);
        }

        return undefined;
    };

    var _setInputRangeW3 = function(input, startPos, endPos) {
        input.setSelectionRange(startPos, endPos);
    };

    var _setInputRangeIE = function(input, startPos, endPos) {
        var i;
        var tr = input.createTextRange();

        // Fix IE from counting the newline characters as two separate characters
        var stop_it = startPos;

        // TODO: Optimize this awful code
        for (i = 0; i < stop_it; i++) {
            if (input.value.substr(i, 1).search(_rNewlineIE) !== -1) {
                startPos = startPos - 1;
            }
        }

        stop_it = endPos;

        // TODO: Optimize this awful code
        for (i = 0; i < stop_it; i++) {
            if (input.value.substr(i, 1).search(_rNewlineIE) !== -1) {
                endPos = endPos - 1;
            }
        }

        tr.moveEnd('textedit', -1);
        tr.moveStart('character', startPos);
        tr.moveEnd('character', endPos - startPos);
        tr.select();
    };

    /**
     * Sets the selected text range of (i.e., highlights text in) the given input.
     * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
     * @param {Number} startPos Zero-based index
     * @param {Number} endPos Zero-based index
     * @see http://parentnode.org/javascript/working-with-the-cursor-position/
     * @see http://stackoverflow.com/a/2966703/467582
     */
    var _setInputRange = function(input, startPos, endPos) {
        // Mozilla, et al.
        if (_support.setSelectionRange) {
            _setInputRangeW3(input, startPos, endPos);
        }
        // IE
        else if (_support.createTextRange) {
            _setInputRangeIE(input, startPos, endPos);
        }
    };

    /**
     * Replaces the currently selected text with the given string.
     * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
     * @param {String} text New text that will replace the currently selected text.
     * @see http://parentnode.org/javascript/working-with-the-cursor-position/
     */
    var _replaceInputRange = function(input, text) {
        var $input = $(input);

        var oldValue = $input.val();
        var selection = _getInputRange(input);

        var newLength = +(selection.start + text.length + (oldValue.length - selection.end));
        var maxLength = +$input.attr('maxlength');

        if($input.is('[maxlength]') && newLength > maxLength) {
            var delta = text.length - (newLength - maxLength);
            text = text.substr(0, delta);
        }

        // Now that we know what the user selected, we can replace it
        var startText = oldValue.substr(0, selection.start);
        var endText = oldValue.substr(selection.end);
        $input.val(startText + text + endText);

        // Reset the selection
        var startPos = selection.start;
        var endPos = startPos + text.length;

        _setInputRange(input, selection.length ? startPos : endPos, endPos);
    };

    var _selectAllW3 = function(elem) {
        var selection = window.getSelection();
        var range = document.createRange();
        range.selectNodeContents(elem);
        selection.removeAllRanges();
        selection.addRange(range);
    };

    var _selectAllIE = function(elem) {
        var range = document.body.createTextRange();
        range.moveToElementText(elem);
        range.select();
    };

    /**
     * Select all text in the given element.
     * @param {HTMLElement} elem Any block or inline element other than a form element.
     */
    var _highlight = function(elem) {
        // Mozilla, et al.
        if (_support.setSelectionRange) {
            _selectAllW3(elem);
        }
        // IE
        else if (_support.createTextRange) {
            _selectAllIE(elem);
        }
    };

    $.fn.extend({

        /**
         * Gets or sets the position of the caret or inserts text at the current caret position in an input or textarea element.
         * @returns {Number|jQuery} The current caret position if invoked as a getter (with no arguments)
         * or this jQuery object if invoked as a setter or inserter.
         * @see http://web.archive.org/web/20080704185920/http://parentnode.org/javascript/working-with-the-cursor-position/
         * @since 1.0.0
         * @example
         *

         *    // Get position
         *    var pos = $('input:first').caret();
         *

         * @example
         *

         *    // Set position
         *    $('input:first').caret(15);
         *    $('input:first').caret(-3);
         *

         * @example
         *

         *    // Insert text at current position
         *    $('input:first').caret('Some text');
         *

         */
        caret: function() {
            var $inputs = this.filter('input, textarea');

            // getCaret()
            if (arguments.length === 0) {
                var input = $inputs.get(0);
                return _getCaret(input);
            }
            // setCaret(position)
            else if (typeof arguments[0] === 'number') {
                var arg0 = arguments[0];
                $inputs.each(function(_i, input) {
                    var pos = _getIndex(input, arg0);
                    _setCaret(input, pos);
                });
            }
            // insertAtCaret(text)
            else {
                var text = arguments[0];
                $inputs.each(function(_i, input) {
                    _insertAtCaret(input, text);
                });
            }

            return this;
        },

        /**
         * Gets or sets the selection range or replaces the currently selected text in an input or textarea element.
         * @returns {Range|jQuery} The current selection range if invoked as a getter (with no arguments)
         * or this jQuery object if invoked as a setter or replacer.
         * @see http://stackoverflow.com/a/2966703/467582
         * @since 1.0.0
         * @example
         *

         *    // Get selection range
         *    var range = $('input:first').range();
         *

         * @example
         *

         *    // Set selection range
         *    $('input:first').range(15);
         *    $('input:first').range(15, 20);
         *    $('input:first').range(-3);
         *    $('input:first').range(-8, -3);
         *

         * @example
         *

         *    // Replace the currently selected text
         *    $('input:first').range('Replacement text');
         *

         */
        range: function() {
            var $inputs = this.filter('input, textarea');

            // getRange() = { start: pos, end: pos }
            if (arguments.length === 0) {
                var input = $inputs.get(0);
                return _getInputRange(input);
            }
            // setRange(startPos, endPos)
            else if (typeof arguments[0] === 'number') {
                var arg0 = arguments[0];
                var arg1 = arguments[1];
                $inputs.each(function(_i, input) {
                    var startPos = _getIndex(input, arg0),
                        endPos = _getIndex(input, arg1);
                    _setInputRange(input, startPos, endPos);
                });
            }
            // replaceRange(text)
            else {
                var text = arguments[0];
                $inputs.each(function(_i, input) {
                    _replaceInputRange(input, text);
                });
            }

            return this;
        },

        /**
         * Highlights (selects) all text in each element of this jQuery object.
         * @returns {jQuery} This jQuery object
         * @see http://stackoverflow.com/a/11128179/467582
         * @since 1.1.0
         * @example
         *

         *     // Highlight the contents of span elements when clicked
         *     $('span').on('click', function() { $(this).highlight(); });
         *

         */
        highlight: function() {
            return this.each(function(_i, elem) {
                _highlight(elem);
            });
        }

    });
}(jQuery));<commit_msg>Removed alert from jquery.caret.js<commit_after>/*! jQuery Caret plugin v1.3.0 | (c) 2012, 2013 Andrew C. Dvorak | github.com/acdvorak/jquery.caret */
(function($, undefined) {

    var _input = document.createElement('input');

    var _support = {
        setSelectionRange: ('setSelectionRange' in _input) || ('selectionStart' in _input),
        createTextRange: ('createTextRange' in _input) || ('selection' in document)
    };

    var _rNewlineIE = /\r\n/g,
        _rCarriageReturn = /\r/g;

    var _format = function() {
        var str = arguments[0];
        var args = [].slice.call(arguments, 1);
        return str.replace(/{(\d+)}/g, function(match, number) {
            return typeof(args[number]) !== 'undefined'
                ? args[number]
                : match
                ;
        });
    };

    var _getIndex = function(input, pos) {
        var norm = input.value.replace(_rCarriageReturn, '');
        var len = norm.length;

        if (typeof(pos) === 'undefined') {
            pos = len;
        }

        pos = Math.floor(pos);

        // Negative index counts backward from the end of the input/textarea's value
        if (pos < 0) {
            pos = len + pos;
        }

        return pos;
    };

    var _hasAttr = function(input, attrName) {
        return input.hasAttribute ? input.hasAttribute(attrName) : (typeof(input[attrName]) !== 'undefined');
    };

    /**
     * @class
     * @constructor
     */
    var Range = function(start, end, length, text) {
        this.start = start || 0;
        this.end = end || 0;
        this.length = length || 0;
        this.text = text || '';
    };

    var _getCaretW3 = function(input) {
        return input.selectionStart;
    };

    /**
     * @see http://stackoverflow.com/q/6943000/467582
     */
    var _getCaretIE = function(input) {
        var caret, range, textInputRange, len, endRange;

        // Yeah, you have to focus twice for IE 7 and 8.  *cries*
        input.focus();
        input.focus();

        range = document.selection.createRange();

        if (range && range.parentElement() === input) {
            len = input.value.length;

            // Create a working TextRange that lives only in the input
            textInputRange = input.createTextRange();
            textInputRange.moveToBookmark(range.getBookmark());

            // Check if the start and end of the selection are at the very end
            // of the input, since moveStart/moveEnd doesn't return what we want
            // in those cases
            endRange = input.createTextRange();
            endRange.collapse(false);

            if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
                caret = input.value.replace(_rNewlineIE, '\n').length;
            } else {
                caret = -textInputRange.moveStart("character", -len);
            }

            return caret;
        }

        // TODO: This can occur when you highlight part of a textarea and then click in the middle of the highlighted portion in IE 6-10
//        alert("Your browser is incredibly stupid.  I don't know what else to say.");
//        alert(range + '\n\n' + range.parentElement().tagName + '#' + range.parentElement().id);

        return 0;
    };

    /**
     * Gets the position of the caret in the given input.
     * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
     * @returns {Number}
     * @see http://stackoverflow.com/questions/263743/how-to-get-cursor-position-in-textarea/263796#263796
     */
    var _getCaret = function(input) {
        if (!input) {
            return undefined;
        }

        // Mozilla, et al.
        if (_support.setSelectionRange) {
            return _getCaretW3(input);
        }
        // IE
        else if (_support.createTextRange) {
            return _getCaretIE(input);
        }

        return undefined;
    };

    var _setCaretW3 = function(input, pos) {
        input.setSelectionRange(pos, pos);
    };

    var _setCaretIE = function(input, pos) {
        var range = input.createTextRange();
        range.move('character', pos);
        range.select();
    };

    /**
     * Sets the position of the caret in the given input.
     * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
     * @param {Number} pos
     * @see http://parentnode.org/javascript/working-with-the-cursor-position/
     */
    var _setCaret = function(input, pos) {
        input.focus();

        // Mozilla, et al.
        if (_support.setSelectionRange) {
            _setCaretW3(input, pos);
        }
        // IE
        else if (_support.createTextRange) {
            _setCaretIE(input, pos);
        }
    };

    /**
     * Inserts the specified text at the current caret position in the given input.
     * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
     * @param {String} text
     * @see http://parentnode.org/javascript/working-with-the-cursor-position/
     */
    var _insertAtCaret = function(input, text) {
        var curPos = _getCaret(input);

        var oldValueNorm = input.value.replace(_rCarriageReturn, '');

        var newLength = +(curPos + text.length + (oldValueNorm.length - curPos));
        var maxLength = +input.getAttribute('maxlength');

        if(_hasAttr(input, 'maxlength') && newLength > maxLength) {
            var delta = text.length - (newLength - maxLength);
            text = text.substr(0, delta);
        }

        input.value = oldValueNorm.substr(0, curPos) + text + oldValueNorm.substr(curPos);

        _setCaret(input, curPos + text.length);
    };

    var _getInputRangeW3 = function(input) {
        var range = new Range();

        range.start = input.selectionStart;
        range.end = input.selectionEnd;

        var min = Math.min(range.start, range.end);
        var max = Math.max(range.start, range.end);

        range.length = max - min;
        range.text = input.value.substring(min, max);

        return range;
    };

    var _getInputRangeIE = function(input) {
        var range = new Range();

        input.focus();

        var selection = document.selection.createRange();

        if (!selection) {
            return range;
        }

        var tr_beginning = input.createTextRange();
        var tr_selection = tr_beginning.duplicate();

        tr_selection.moveToBookmark(selection.getBookmark());
        tr_beginning.setEndPoint('EndToStart', tr_selection);

        range.start = tr_beginning.text.replace(_rNewlineIE, '\n').length;
        range.text = selection.text.replace(_rNewlineIE, '\n').replace(_rCarriageReturn, '\n');

        range.length = range.text.length;
        range.end = range.start + range.length;

        if(range.length === 0) {
            range.start = range.end = _getCaret(input);
        }

        return range;
    };

    /**
     * Gets the selected text range of the given input.
     * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
     * @returns {Range}
     * @see http://stackoverflow.com/a/263796/467582
     * @see http://stackoverflow.com/a/2966703/467582
     */
    var _getInputRange = function(input) {
        if (!input) {
            return undefined;
        }

        // Mozilla, et al.
        if (_support.setSelectionRange) {
            return _getInputRangeW3(input);
        }
        // IE
        else if (_support.createTextRange) {
            return _getInputRangeIE(input);
        }

        return undefined;
    };

    var _setInputRangeW3 = function(input, startPos, endPos) {
        input.setSelectionRange(startPos, endPos);
    };

    var _setInputRangeIE = function(input, startPos, endPos) {
        var i;
        var tr = input.createTextRange();

        // Fix IE from counting the newline characters as two separate characters
        var stop_it = startPos;

        // TODO: Optimize this awful code
        for (i = 0; i < stop_it; i++) {
            if (input.value.substr(i, 1).search(_rNewlineIE) !== -1) {
                startPos = startPos - 1;
            }
        }

        stop_it = endPos;

        // TODO: Optimize this awful code
        for (i = 0; i < stop_it; i++) {
            if (input.value.substr(i, 1).search(_rNewlineIE) !== -1) {
                endPos = endPos - 1;
            }
        }

        tr.moveEnd('textedit', -1);
        tr.moveStart('character', startPos);
        tr.moveEnd('character', endPos - startPos);
        tr.select();
    };

    /**
     * Sets the selected text range of (i.e., highlights text in) the given input.
     * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
     * @param {Number} startPos Zero-based index
     * @param {Number} endPos Zero-based index
     * @see http://parentnode.org/javascript/working-with-the-cursor-position/
     * @see http://stackoverflow.com/a/2966703/467582
     */
    var _setInputRange = function(input, startPos, endPos) {
        // Mozilla, et al.
        if (_support.setSelectionRange) {
            _setInputRangeW3(input, startPos, endPos);
        }
        // IE
        else if (_support.createTextRange) {
            _setInputRangeIE(input, startPos, endPos);
        }
    };

    /**
     * Replaces the currently selected text with the given string.
     * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element
     * @param {String} text New text that will replace the currently selected text.
     * @see http://parentnode.org/javascript/working-with-the-cursor-position/
     */
    var _replaceInputRange = function(input, text) {
        var $input = $(input);

        var oldValue = $input.val();
        var selection = _getInputRange(input);

        var newLength = +(selection.start + text.length + (oldValue.length - selection.end));
        var maxLength = +$input.attr('maxlength');

        if($input.is('[maxlength]') && newLength > maxLength) {
            var delta = text.length - (newLength - maxLength);
            text = text.substr(0, delta);
        }

        // Now that we know what the user selected, we can replace it
        var startText = oldValue.substr(0, selection.start);
        var endText = oldValue.substr(selection.end);
        $input.val(startText + text + endText);

        // Reset the selection
        var startPos = selection.start;
        var endPos = startPos + text.length;

        _setInputRange(input, selection.length ? startPos : endPos, endPos);
    };

    var _selectAllW3 = function(elem) {
        var selection = window.getSelection();
        var range = document.createRange();
        range.selectNodeContents(elem);
        selection.removeAllRanges();
        selection.addRange(range);
    };

    var _selectAllIE = function(elem) {
        var range = document.body.createTextRange();
        range.moveToElementText(elem);
        range.select();
    };

    /**
     * Select all text in the given element.
     * @param {HTMLElement} elem Any block or inline element other than a form element.
     */
    var _highlight = function(elem) {
        // Mozilla, et al.
        if (_support.setSelectionRange) {
            _selectAllW3(elem);
        }
        // IE
        else if (_support.createTextRange) {
            _selectAllIE(elem);
        }
    };

    $.fn.extend({

        /**
         * Gets or sets the position of the caret or inserts text at the current caret position in an input or textarea element.
         * @returns {Number|jQuery} The current caret position if invoked as a getter (with no arguments)
         * or this jQuery object if invoked as a setter or inserter.
         * @see http://web.archive.org/web/20080704185920/http://parentnode.org/javascript/working-with-the-cursor-position/
         * @since 1.0.0
         * @example
         *

         *    // Get position
         *    var pos = $('input:first').caret();
         *

         * @example
         *

         *    // Set position
         *    $('input:first').caret(15);
         *    $('input:first').caret(-3);
         *

         * @example
         *

         *    // Insert text at current position
         *    $('input:first').caret('Some text');
         *

         */
        caret: function() {
            var $inputs = this.filter('input, textarea');

            // getCaret()
            if (arguments.length === 0) {
                var input = $inputs.get(0);
                return _getCaret(input);
            }
            // setCaret(position)
            else if (typeof arguments[0] === 'number') {
                var arg0 = arguments[0];
                $inputs.each(function(_i, input) {
                    var pos = _getIndex(input, arg0);
                    _setCaret(input, pos);
                });
            }
            // insertAtCaret(text)
            else {
                var text = arguments[0];
                $inputs.each(function(_i, input) {
                    _insertAtCaret(input, text);
                });
            }

            return this;
        },

        /**
         * Gets or sets the selection range or replaces the currently selected text in an input or textarea element.
         * @returns {Range|jQuery} The current selection range if invoked as a getter (with no arguments)
         * or this jQuery object if invoked as a setter or replacer.
         * @see http://stackoverflow.com/a/2966703/467582
         * @since 1.0.0
         * @example
         *

         *    // Get selection range
         *    var range = $('input:first').range();
         *

         * @example
         *

         *    // Set selection range
         *    $('input:first').range(15);
         *    $('input:first').range(15, 20);
         *    $('input:first').range(-3);
         *    $('input:first').range(-8, -3);
         *

         * @example
         *

         *    // Replace the currently selected text
         *    $('input:first').range('Replacement text');
         *

         */
        range: function() {
            var $inputs = this.filter('input, textarea');

            // getRange() = { start: pos, end: pos }
            if (arguments.length === 0) {
                var input = $inputs.get(0);
                return _getInputRange(input);
            }
            // setRange(startPos, endPos)
            else if (typeof arguments[0] === 'number') {
                var arg0 = arguments[0];
                var arg1 = arguments[1];
                $inputs.each(function(_i, input) {
                    var startPos = _getIndex(input, arg0),
                        endPos = _getIndex(input, arg1);
                    _setInputRange(input, startPos, endPos);
                });
            }
            // replaceRange(text)
            else {
                var text = arguments[0];
                $inputs.each(function(_i, input) {
                    _replaceInputRange(input, text);
                });
            }

            return this;
        },

        /**
         * Highlights (selects) all text in each element of this jQuery object.
         * @returns {jQuery} This jQuery object
         * @see http://stackoverflow.com/a/11128179/467582
         * @since 1.1.0
         * @example
         *

         *     // Highlight the contents of span elements when clicked
         *     $('span').on('click', function() { $(this).highlight(); });
         *

         */
        highlight: function() {
            return this.each(function(_i, elem) {
                _highlight(elem);
            });
        }

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


<commit_before>var ent     = require("./ent"),
Base        = require("./base"),
protoclass  = require("protoclass");

function Node () {

}

protoclass(Node, {
  __isNode: true
});

function Container () {
  this.childNodes = [];
}

protoclass(Node, Container, {

  /**
   */

  appendChild: function (node) {

    if (node.nodeType === 11) {
      while (node.childNodes.length) {
        this.appendChild(node.childNodes[0]);
      }
      return;
    }

    this._unlink(node);
    this.childNodes.push(node);
    this._link(node);
  },

  /**
   */

  prependChild: function (node) {
    if (!this.childNodes.length) {
      this.appendChild(node);
    } else {
      this.insertBefore(node, this.childNodes[0]);
    }
  },

  /**
   */

  removeChild: function (child) {
    var i = this.childNodes.indexOf(child);

    if (!~i) return;

    this.childNodes.splice(i, 1);

    if (child.previousSibling) child.previousSibling.nextSibling = child.nextSibling;
    if (child.nextSibling)     child.nextSibling.previousSibling = child.previousSibling;

    delete child.parentNode;
    delete child.nextSibling;
    delete child.previousSibling;
  },

  /**
   */

  insertBefore: function (newElement, before) {

    if (newElement.nodeType === 11) {
      var before, node;
      for (var i = newElement.childNodes.length; i--;) {
        this.insertBefore(node = newElement.childNodes[i], before);
        before = node;
      }
    }

    this._splice(this.childNodes.indexOf(before), 0, newElement);
  },

  /**
   */

  _splice: function (index, count, node) {

    if (typeof index === "undefined") index = -1;
    if (!~index) return;

    if (node) this._unlink(node);

    this.childNodes.splice.apply(this.childNodes, arguments);

    if (node) this._link(node);
  },

  /**
   */

  _unlink: function (node) {
    if (node.parentNode) {
      node.parentNode.removeChild(node);
    }
  },

  /**
   */

  _link: function (node) {

    if (!node.__isNode) {
      throw new Error("cannot append non-node");
    }

    node.parentNode = this;
    var i = this.childNodes.indexOf(node);

    // FFox compatible
    if (i !== 0)                         node.previousSibling = this.childNodes[i - 1];
    if (i != this.childNodes.length - 1) node.nextSibling     = this.childNodes[i + 1];

    if (node.previousSibling) node.previousSibling.nextSibling = node;
    if (node.nextSibling)     node.nextSibling.previousSibling = node;
  }
});

function Style () {

}

protoclass(Style, {

  /**
   */

  _hasStyle: false,

  /**
   */

  setProperty: function(key, value) {

    if (value === "" || value == undefined) {
      delete this[key];
      return;
    }

    this[key] = value;
  },

  /**
   */

  parse: function (styles) {

    var styleParts = styles.split(/;\s*/);

    for (var i = styleParts.length; i--;) {
      var sp = styleParts[i].split(/:\s*/);

      if (sp[i] == undefined || sp[1] == "") {
        continue;
      }

      this[sp[0]] = sp[1];
    }
  },

  /**
   */

  toString: function () {
    var buffer = [];
    for (var key in this) {
      if(this.constructor.prototype[key] !== undefined) continue;

      var v = this[key];

      if (v === "") {
        continue;
      }

      buffer.push(key + ": " + this[key]);
    }

    if(!buffer.length) return "";

    return buffer.join("; ") + ";"
  },

  /**
   */

  hasStyles: function () {
    if(this._hasStyle) return true;

    for (var key in this) {
      if (this[key] != undefined && this.constructor.prototype[key] == undefined) {
        return this._hasStyle = true;
      }
    }

    return false;
  }
});

function Element (nodeName) {
  Element.superclass.call(this);

  this.nodeName    = nodeName.toUpperCase();
  this._name       = nodeName.toLowerCase();
  this.attributes  = [];
  this._attrsByKey = {};
  this.style       = new Style();

}

protoclass(Container, Element, {

  /**
   */

  nodeType: 3,

  /**
   */

  setAttribute: function (name, value) {
    name = name.toLowerCase();

    if (name === "style") {
      return this.style.parse(value);
    }

    if (value == undefined) {
      return this.removeAttribute(name);
    }

    var abk;

    if (!(abk = this._attrsByKey[name])) {
      this.attributes.push(abk = this._attrsByKey[name] = {})
    }

    abk.name  = name;
    abk.value = value;
  },

  /**
   */

  removeAttribute: function (name) {

    for (var i = this.attributes.length; i--;) {
      var attr = this.attributes[i];
      if (attr.name == name) {
        this.attributes.splice(i, 1);
        break;
      }
    }

    delete this._attrsByKey[name];
  },

  /**
   */

  getAttribute: function (name) {
    var abk;
    if(abk = this._attrsByKey[name]) return abk.value;
  },

  /**
   */

  toString: function () {

    var buffer = ["<", this._name],
    attribs    =  [],
    attrbuff;

    for (var name in this._attrsByKey) {

      var v    = this._attrsByKey[name].value;
      attrbuff = name;

      if (name != undefined) {
        attrbuff += "=\"" + v + "\"";
      }

      attribs.push(attrbuff);
    }

    if (this.style.hasStyles()) {
      attribs.push("style=" + "\"" + this.style.toString() + "\"");
    }

    if (attribs.length) {
      buffer.push(" ", attribs.join(" "));
    }

    buffer.push(">");
    buffer.push.apply(buffer, this.childNodes);
    buffer.push("</", this._name, ">");

    return buffer.join("");
  }
});

function Text (value, encode) {

  if (encode) {
    value = ent(value);
  }

  this.value = value;
}

protoclass(Node, Text, {

  /**
   */

  nodeType: 3,

  /**
   */

  toString: function () {
    return this.value;
  }
});

function Comment () {
  Comment.superclass.apply(this, arguments);
}

protoclass(Text, Comment, {

  /**
   */

  nodeType: 8,

  /**
   */

  toString: function () {
    return "<!--" + Comment.__super__.toString.call(this) + "-->";
  }
});

function Fragment () {
  Fragment.superclass.call(this);
}

protoclass(Container, Fragment, {

  /**
   */

  nodeType: 11,

  /**
   */

  toString: function () {
    return this.childNodes.join("");
  }
});

function StringNodeFactory (context) {
  this.context = context;
}

protoclass(Base, StringNodeFactory, {

  /**
   */

  name: "string",

  /**
   */

  createElement: function (name) {
    return new Element(name);
  },

  /**
   */

  createTextNode: function (value, encode) {
    return new Text(value, encode);
  },

  /**
   */

  createComment: function (value) {
    return new Comment(value);
  },

  /**
   */

  createFragment: function (children) {
    if (!children) children = [];
    var frag = new Fragment(),
    childrenToArray = Array.prototype.slice.call(children, 0);

    for (var i = 0, n = childrenToArray.length; i < n; i++) {
      frag.appendChild(childrenToArray[i]);
    }

    return frag;
  },

  /**
   */

  parseHtml: function (buffer) {

    //this should really parse HTML, but too much overhead
    return this.createTextNode(buffer);
  }
});

module.exports = new StringNodeFactory();<commit_msg>cleanup<commit_after>var ent     = require("./ent"),
Base        = require("./base"),
protoclass  = require("protoclass");

function Node () {

}

protoclass(Node, {
  __isNode: true
});

function Container () {
  this.childNodes = [];
}

protoclass(Node, Container, {

  /**
   */

  appendChild: function (node) {

    if (node.nodeType === 11) {
      while (node.childNodes.length) {
        this.appendChild(node.childNodes[0]);
      }
      return;
    }

    this._unlink(node);
    this.childNodes.push(node);
    this._link(node);
  },

  /**
   */

  prependChild: function (node) {
    if (!this.childNodes.length) {
      this.appendChild(node);
    } else {
      this.insertBefore(node, this.childNodes[0]);
    }
  },

  /**
   */

  removeChild: function (child) {
    var i = this.childNodes.indexOf(child);

    if (!~i) return;

    this.childNodes.splice(i, 1);

    if (child.previousSibling) child.previousSibling.nextSibling = child.nextSibling;
    if (child.nextSibling)     child.nextSibling.previousSibling = child.previousSibling;

    delete child.parentNode;
    delete child.nextSibling;
    delete child.previousSibling;
  },

  /**
   */

  insertBefore: function (newElement, before) {

    if (newElement.nodeType === 11) {
      var before, node;
      for (var i = newElement.childNodes.length; i--;) {
        this.insertBefore(node = newElement.childNodes[i], before);
        before = node;
      }
    }

    this._splice(this.childNodes.indexOf(before), 0, newElement);
  },

  /**
   */

  _splice: function (index, count, node) {

    if (typeof index === "undefined") index = -1;
    if (!~index) return;

    if (node) this._unlink(node);

    this.childNodes.splice.apply(this.childNodes, arguments);

    if (node) this._link(node);
  },

  /**
   */

  _unlink: function (node) {
    if (node.parentNode) {
      node.parentNode.removeChild(node);
    }
  },

  /**
   */

  _link: function (node) {

    if (!node.__isNode) {
      throw new Error("cannot append non-node");
    }

    node.parentNode = this;
    var i = this.childNodes.indexOf(node);

    // FFox compatible
    if (i !== 0)                         node.previousSibling = this.childNodes[i - 1];
    if (i != this.childNodes.length - 1) node.nextSibling     = this.childNodes[i + 1];

    if (node.previousSibling) node.previousSibling.nextSibling = node;
    if (node.nextSibling)     node.nextSibling.previousSibling = node;
  }
});

function Style () {

}

protoclass(Style, {

  /**
   */

  _hasStyle: false,

  /**
   */

  setProperty: function(key, value) {

    if (value === "" || value == undefined) {
      delete this[key];
      return;
    }

    this[key] = value;
  },

  /**
   */

  parse: function (styles) {
    var styleParts = styles.split(/;\s*/);

    for (var i = styleParts.length; i--;) {
      var sp = styleParts[i].split(/:\s*/);

      if (sp[i] == undefined || sp[1] == "") {
        continue;
      }

      this[sp[0]] = sp[1];
    }
  },

  /**
   */

  toString: function () {
    var buffer = [];
    for (var key in this) {
      if(this.constructor.prototype[key] !== undefined) continue;

      var v = this[key];

      if (v === "") {
        continue;
      }

      buffer.push(key + ": " + this[key]);
    }

    if(!buffer.length) return "";

    return buffer.join("; ") + ";"
  },

  /**
   */

  hasStyles: function () {
    if(this._hasStyle) return true;

    for (var key in this) {
      if (this[key] != undefined && this.constructor.prototype[key] == undefined) {
        return this._hasStyle = true;
      }
    }

    return false;
  }
});

function Element (nodeName) {
  Element.superclass.call(this);

  this.nodeName    = nodeName.toUpperCase();
  this._name       = nodeName.toLowerCase();
  this.attributes  = [];
  this._attrsByKey = {};
  this.style       = new Style();

}

protoclass(Container, Element, {

  /**
   */

  nodeType: 3,

  /**
   */

  setAttribute: function (name, value) {
    name = name.toLowerCase();

    if (name === "style") {
      return this.style.parse(value);
    }

    if (value == undefined) {
      return this.removeAttribute(name);
    }

    var abk;

    if (!(abk = this._attrsByKey[name])) {
      this.attributes.push(abk = this._attrsByKey[name] = {})
    }

    abk.name  = name;
    abk.value = value;
  },

  /**
   */

  removeAttribute: function (name) {

    for (var i = this.attributes.length; i--;) {
      var attr = this.attributes[i];
      if (attr.name == name) {
        this.attributes.splice(i, 1);
        break;
      }
    }

    delete this._attrsByKey[name];
  },

  /**
   */

  getAttribute: function (name) {
    var abk;
    if(abk = this._attrsByKey[name]) return abk.value;
  },

  /**
   */

  toString: function () {

    var buffer = ["<", this._name],
    attribs    =  [],
    attrbuff;

    for (var name in this._attrsByKey) {

      var v    = this._attrsByKey[name].value;
      attrbuff = name;

      if (name != undefined) {
        attrbuff += "=\"" + v + "\"";
      }

      attribs.push(attrbuff);
    }

    if (this.style.hasStyles()) {
      attribs.push("style=" + "\"" + this.style.toString() + "\"");
    }

    if (attribs.length) {
      buffer.push(" ", attribs.join(" "));
    }

    buffer.push(">");
    buffer.push.apply(buffer, this.childNodes);
    buffer.push("</", this._name, ">");

    return buffer.join("");
  }
});

function Text (value, encode) {

  if (encode) {
    value = ent(value);
  }

  this.value = value;
}

protoclass(Node, Text, {

  /**
   */

  nodeType: 3,

  /**
   */

  toString: function () {
    return this.value;
  }
});

function Comment () {
  Comment.superclass.apply(this, arguments);
}

protoclass(Text, Comment, {

  /**
   */

  nodeType: 8,

  /**
   */

  toString: function () {
    return "<!--" + Comment.__super__.toString.call(this) + "-->";
  }
});

function Fragment () {
  Fragment.superclass.call(this);
}

protoclass(Container, Fragment, {

  /**
   */

  nodeType: 11,

  /**
   */

  toString: function () {
    return this.childNodes.join("");
  }
});

function StringNodeFactory (context) {
  this.context = context;
}

protoclass(Base, StringNodeFactory, {

  /**
   */

  name: "string",

  /**
   */

  createElement: function (name) {
    return new Element(name);
  },

  /**
   */

  createTextNode: function (value, encode) {
    return new Text(value, encode);
  },

  /**
   */

  createComment: function (value) {
    return new Comment(value);
  },

  /**
   */

  createFragment: function (children) {
    if (!children) children = [];
    var frag = new Fragment(),
    childrenToArray = Array.prototype.slice.call(children, 0);

    for (var i = 0, n = childrenToArray.length; i < n; i++) {
      frag.appendChild(childrenToArray[i]);
    }

    return frag;
  },

  /**
   */

  parseHtml: function (buffer) {

    //this should really parse HTML, but too much overhead
    return this.createTextNode(buffer);
  }
});

module.exports = new StringNodeFactory();<|endoftext|>


<commit_before>(function(global) {
  'use strict';

  function Option(hasValue, val) {

    function valueOf() {
      return val;
    }

    function isSome() {
      return hasValue;
    }

    function isNone() {
      return !hasValue;
    }

    function valueOrElse(els) {
      return hasValue ? val : els;
    }

    var optionMethods = {
      valueOf: valueOf,
      isSome: isSome,
      isNone: isNone,
      valueOrElse: valueOrElse
    };

    ['map', 'filter', 'forEach', 'every', 'sort', 'reverse'].forEach(function(method) {
      optionMethods[method] = function() {
        var result = Array.prototype[method].apply((hasValue ? [val] : []), arguments);
        return result[0];
      };
    });

    ['forEach'].forEach(function(method) {
      optionMethods[method] = function() {
        Array.prototype[method].apply((hasValue ? [val] : []), arguments);
      };
    });

    ['every', 'some', 'reduce', 'reduceRight', 'join', 'slice', 'lastIndexOf', 'concat', 'indexOf'].forEach(function(method) {
      optionMethods[method] = function() {
        var result = Array.prototype[method].apply((hasValue ? [val] : []), arguments);
        return result;
      };
    });

    return optionMethods;
  }

  // Export
  var exported = {
    Some: function Some(val) {
      return Option(true, val);
    },
    None: function None() {
      return Option(false);
    }
  };

  (function() {
    /*global module: false */
    /*global define: false */
    if (typeof module !== 'undefined' && module !== null) {
      // CommonJS
      module.exports = exported;
    } else {
      if (typeof define === 'function') {
        // AMD RequireJS
        define('option', function() {
          return exported;
        });
      }

      global.Option = exported;
    }
  })();

})(this);<commit_msg>forEach doesn't need its own block<commit_after>(function(global) {
  'use strict';

  function Option(hasValue, val) {

    function valueOf() {
      return val;
    }

    function isSome() {
      return hasValue;
    }

    function isNone() {
      return !hasValue;
    }

    function valueOrElse(els) {
      return hasValue ? val : els;
    }

    var optionMethods = {
      valueOf: valueOf,
      isSome: isSome,
      isNone: isNone,
      valueOrElse: valueOrElse
    };

    ['map', 'filter', 'forEach', 'every', 'sort', 'reverse'].forEach(function(method) {
      optionMethods[method] = function() {
        return Array.prototype[method].apply((hasValue ? [val] : []), arguments)[0];
      };
    });

    ['forEach', 'every', 'some', 'reduce', 'reduceRight', 'join', 'slice', 'lastIndexOf', 'concat', 'indexOf'].forEach(function(method) {
      optionMethods[method] = function() {
        return Array.prototype[method].apply((hasValue ? [val] : []), arguments);
      };
    });

    return optionMethods;
  }

  // Export
  var exported = {
    Some: function Some(val) {
      return Option(true, val);
    },
    None: function None() {
      return Option(false);
    }
  };

  (function() {
    /*global module: false */
    /*global define: false */
    if (typeof module !== 'undefined' && module !== null) {
      // CommonJS
      module.exports = exported;
    } else {
      if (typeof define === 'function') {
        // AMD RequireJS
        define('option', function() {
          return exported;
        });
      }

      global.Option = exported;
    }
  })();

})(this);<|endoftext|>


<commit_before>import { Template } from 'meteor/templating';
import { $ } from 'meteor/jquery';
import Events from '/logic/events/schema';
import Calendar from './calendar';
import History from '/views/history/history';
import Sections from '/logic/sections/sections';

/**
 * @summary Register Deep-linking
 * @method
 */
Template.events.onRendered(() => {
  // Get db ID from current instance
  const instanceName = Template.instance().data.name;

  // Register
  if (instanceName) {
    History.registry.insert({
      element: Sections.generateId(instanceName),
      /** Handle deep-linking */
      callback: path => Events.parsePath(path),
    });
  }
});

/**
 * @summary Navigate to an event
 * @method
 *   @param {String} id - The ID of an event to navigate to
 */
Events.navigate = (id) => {
  const thisEvent = Events.collection.findOne({ _id: id });

  // Set Month and year based on event Start date
  Calendar.currentSlide.set(id);
  Calendar.thisYear.set(thisEvent.start.getFullYear());
  Calendar.thisMonth.set(thisEvent.start.getMonth());

  // Close any open asides
  $('.listViewEventsObjectOpen').removeClass('listViewEventsObjectOpen');
  $('.listViewEvents').removeClass('active');
  $('.searchResultsList').removeClass('active');

  // Determine URI path
  const events = document.querySelector('#main > .events');
  const parentName = events.id;
  const path = `${parentName}/${Calendar.thisYear.get()}/${Calendar
    .months[Calendar.thisMonth.get()]}/${Sections.generateId(thisEvent.name)}/`;

  // Update URI using History API
  History.update(parentName, path);

  // Smooth scroll
  const offset = events.offsetTop + 350;

  if ('scrollBehavior' in document.documentElement.style) {
    window.scroll({ top: offset, behavior: 'smooth' });
  } else {
    $('body,html').stop(true, true).animate({ scrollTop: offset }, 750);
  }
};

/**
 * @summary Parse URL path for event data
 * @method
 *   @param {String[]} path - The path by which to navigate
 */
Events.parsePath = (path) => {
  /**
   * @summary Return whether a string is a valid month
   * @function
   *   @param {String} month
   *   @param {Number} index
   */
  const isMonth = i => (month, index) => {
    if (month.toLowerCase() === path[i].toLowerCase()) {
      Calendar.thisMonth.set(index);
      return true;
    }
    return false;
  };

  for (let i = 0; i < path.length; i += 1) {
    // Year
    if (path[i].match(/^\d{4}$/)) {
      Calendar.thisYear.set(Number(path[i]));
    }
    // Specific event ID
    if (path[i].match(/^[a-z0-9]{17}$/i)) {
      Events.navigate(path[i]);
    }

    // Check for month
    Calendar.months.forEach(isMonth(i));

    // TODO(micchickenburger): Check for event title
  }
};
<commit_msg>Console test.<commit_after>import { Template } from 'meteor/templating';
import { $ } from 'meteor/jquery';
import Events from '/logic/events/schema';
import Calendar from './calendar';
import History from '/views/history/history';
import Sections from '/logic/sections/sections';

/**
 * @summary Register Deep-linking
 * @method
 */
Template.events.onRendered(() => {
  // Get db ID from current instance
  const instanceName = Template.instance().data.name;

  // Register
  if (instanceName) {
    History.registry.insert({
      element: Sections.generateId(instanceName),
      /** Handle deep-linking */
      callback: path => Events.parsePath(path),
    });
  }
});

/**
 * @summary Navigate to an event
 * @method
 *   @param {String} id - The ID of an event to navigate to
 */
Events.navigate = (id) => {
  const thisEvent = Events.collection.findOne({ _id: id });

  // Set Month and year based on event Start date
  Calendar.currentSlide.set(id);
  Calendar.thisYear.set(thisEvent.start.getFullYear());
  Calendar.thisMonth.set(thisEvent.start.getMonth());

  // Close any open asides
  $('.listViewEventsObjectOpen').removeClass('listViewEventsObjectOpen');
  $('.listViewEvents').removeClass('active');
  $('.searchResultsList').removeClass('active');

  // Determine URI path
  const events = document.querySelector('#main > .events');
  const parentName = events.id;
  const path = `${parentName}/${Calendar.thisYear.get()}/${Calendar
    .months[Calendar.thisMonth.get()]}/${Sections.generateId(thisEvent.name)}/`;

  // Update URI using History API
  History.update(parentName, path);

  // Smooth scroll
  const offset = events.offsetTop + 350;

  if ('scrollBehavior' in document.documentElement.style) {
    window.scroll({ top: offset, behavior: 'smooth' });
  } else {
    $('body,html').stop(true, true).animate({ scrollTop: offset }, 750);
  }
};

/**
 * @summary Parse URL path for event data
 * @method
 *   @param {String[]} path - The path by which to navigate
 */
Events.parsePath = (path) => {
  /**
   * @summary Return whether a string is a valid month
   * @function
   *   @param {String} month
   *   @param {Number} index
   */
  const isMonth = i => (month, index) => {
    if (month.toLowerCase() === path[i].toLowerCase()) {
      Calendar.thisMonth.set(index);
      return true;
    }
    return false;
  };

  for (let i = 0; i < path.length; i += 1) {
    // Year
    if (path[i].match(/^\d{4}$/)) {
      Calendar.thisYear.set(Number(path[i]));
    }
    // Specific event ID
    if (path[i].match(/^[a-z0-9]{17}$/i)) {
      console.log(path[i]);
      Events.navigate(path[i]);
    }

    // Check for month
    Calendar.months.forEach(isMonth(i));

    // TODO(micchickenburger): Check for event title
  }
};
<|endoftext|>


<commit_before>import {
    DISMISS_EDIT_1RM_SET,
} from 'app/configs+constants/ActionTypes';
import * as SetsActionCreators from 'app/redux/shared_actions/SetsActionCreators';
import * as Analytics from 'app/services/Analytics';
import * as AnalysisSelectors from 'app/redux/selectors/AnalysisSelectors';
import * as SetsSelectors from 'app/redux/selectors/SetsSelectors';
import * as SetUtils from 'app/utility/SetUtils';

export const deleteSet = (setID) => (dispatch, getState) => {
    const state = getState();
    if (SetsSelectors.getIsWorkoutSet(state, setID)) {
        dispatch(SetsActionCreators.deleteWorkoutSet(setID));
    } else {
        dispatch(SetsActionCreators.deleteHistorySet(setID));
    }
};

export const restoreSet = (setID) => (dispatch, getState) => {
    const state = getState();
    if (SetsSelectors.getIsWorkoutSet(state, setID)) {
        dispatch(SetsActionCreators.restoreWorkoutSet(setID));
    } else {
        dispatch(SetsActionCreators.restoreHistorySet(setID));
    }
};

export const removeRep = (setID, repIndex) => (dispatch, getState) => {
    const state = getState();
    if (SetsSelectors.getIsWorkoutSet(state, setID)) {
        dispatch(SetsActionCreators.removeWorkoutRep(setID, repIndex));
    } else {
        dispatch(SetsActionCreators.removeHistoryRep(setID, repIndex));
    }
};

export const restoreRep = (setID, repIndex) => (dispatch, getState) => {
    const state = getState();
    if (SetsSelectors.getIsWorkoutSet(state, setID)) {
        dispatch(SetsActionCreators.restoreWorkoutRep(setID, repIndex));
    } else {
        dispatch(SetsActionCreators.restoreHistoryRep(setID, repIndex));
    }
};

export const dismissEditSet = () => (dispatch, getState) => {
    const state = getState();
    logCloseEditSetAnalytics(state);
    Analytics.setCurrentScreen('analysis');

    dispatch({
        type: DISMISS_EDIT_1RM_SET
    });
};

// ANALYTICS

const logCloseEditSetAnalytics = (state) => {
    const setID = AnalysisSelectors.getSetID(state);
    const set = SetsSelectors.getSet(state, setID);

    const didChangeExercise = AnalysisSelectors.getDidUpdateExerciseName(state, set);
    const didChangeWeight = AnalysisSelectors.getDidUpdateWeight(state, set);
    const didChangeMetric = AnalysisSelectors.getDidUpdateMetric(state, set);
    const didChangeRPE = AnalysisSelectors.getDidUpdateRPE(state, set);
    const didChangeTags = AnalysisSelectors.getDidUpdateTags(state, set);
    const didChangeReps = AnalysisSelectors.getDidUpdateReps(state, set);
    const didDeleteSet = AnalysisSelectors.getDidDeleteSet(state, set);
    const didRestoreSet = AnalysisSelectors.getDidRestoreSet(state, set);
    const wasRemoved = AnalysisSelectors.getWasError(state)

    const didEditSet = didChangeExercise || didChangeWeight || didChangeMetric || didChangeRPE || didChangeTags || didChangeReps || didDeleteSet || didRestoreSet;

    Analytics.logEventWithAppState('one_rm_close_edit_set', {
        did_edit_set: didEditSet,
        did_change_exercise: didChangeExercise,
        did_change_weight: didChangeWeight,
        did_change_metric: didChangeMetric,
        did_change_rpe: didChangeRPE,
        did_change_tags: didChangeTags,
        did_change_reps: didChangeReps,
        did_delete_set: didDeleteSet,
        did_restore_set: didRestoreSet,
        was_removed: wasRemoved,
    }, state);
};
<commit_msg>switched to was_error<commit_after>import {
    DISMISS_EDIT_1RM_SET,
} from 'app/configs+constants/ActionTypes';
import * as SetsActionCreators from 'app/redux/shared_actions/SetsActionCreators';
import * as Analytics from 'app/services/Analytics';
import * as AnalysisSelectors from 'app/redux/selectors/AnalysisSelectors';
import * as SetsSelectors from 'app/redux/selectors/SetsSelectors';
import * as SetUtils from 'app/utility/SetUtils';

export const deleteSet = (setID) => (dispatch, getState) => {
    const state = getState();
    if (SetsSelectors.getIsWorkoutSet(state, setID)) {
        dispatch(SetsActionCreators.deleteWorkoutSet(setID));
    } else {
        dispatch(SetsActionCreators.deleteHistorySet(setID));
    }
};

export const restoreSet = (setID) => (dispatch, getState) => {
    const state = getState();
    if (SetsSelectors.getIsWorkoutSet(state, setID)) {
        dispatch(SetsActionCreators.restoreWorkoutSet(setID));
    } else {
        dispatch(SetsActionCreators.restoreHistorySet(setID));
    }
};

export const removeRep = (setID, repIndex) => (dispatch, getState) => {
    const state = getState();
    if (SetsSelectors.getIsWorkoutSet(state, setID)) {
        dispatch(SetsActionCreators.removeWorkoutRep(setID, repIndex));
    } else {
        dispatch(SetsActionCreators.removeHistoryRep(setID, repIndex));
    }
};

export const restoreRep = (setID, repIndex) => (dispatch, getState) => {
    const state = getState();
    if (SetsSelectors.getIsWorkoutSet(state, setID)) {
        dispatch(SetsActionCreators.restoreWorkoutRep(setID, repIndex));
    } else {
        dispatch(SetsActionCreators.restoreHistoryRep(setID, repIndex));
    }
};

export const dismissEditSet = () => (dispatch, getState) => {
    const state = getState();
    logCloseEditSetAnalytics(state);
    Analytics.setCurrentScreen('analysis');

    dispatch({
        type: DISMISS_EDIT_1RM_SET
    });
};

// ANALYTICS

const logCloseEditSetAnalytics = (state) => {
    const setID = AnalysisSelectors.getSetID(state);
    const set = SetsSelectors.getSet(state, setID);

    const didChangeExercise = AnalysisSelectors.getDidUpdateExerciseName(state, set);
    const didChangeWeight = AnalysisSelectors.getDidUpdateWeight(state, set);
    const didChangeMetric = AnalysisSelectors.getDidUpdateMetric(state, set);
    const didChangeRPE = AnalysisSelectors.getDidUpdateRPE(state, set);
    const didChangeTags = AnalysisSelectors.getDidUpdateTags(state, set);
    const didChangeReps = AnalysisSelectors.getDidUpdateReps(state, set);
    const didDeleteSet = AnalysisSelectors.getDidDeleteSet(state, set);
    const didRestoreSet = AnalysisSelectors.getDidRestoreSet(state, set);
    const wasRemoved = AnalysisSelectors.getWasError(state)
    const wasError = AnalysisSelectors.getWasError(state)

    const didEditSet = didChangeExercise || didChangeWeight || didChangeMetric || didChangeRPE || didChangeTags || didChangeReps || didDeleteSet || didRestoreSet;

    Analytics.logEventWithAppState('one_rm_close_edit_set', {
        did_edit_set: didEditSet,
        did_change_exercise: didChangeExercise,
        did_change_weight: didChangeWeight,
        did_change_metric: didChangeMetric,
        did_change_rpe: didChangeRPE,
        did_change_tags: didChangeTags,
        did_change_reps: didChangeReps,
        did_delete_set: didDeleteSet,
        did_restore_set: didRestoreSet,
        was_removed: wasRemoved,
        was_error: wasError,
    }, state);
};
<|endoftext|>


<commit_before>(function (root) {

    function getChar(e) {
        var keynum;

        if (window.event) {
            keynum = e.keyCode;
        } else if(e.which){
            keynum = e.which;
        }

        return String.fromCharCode(keynum);
    }

    var _handlers = {
        chars: {},
        shift: {},
        ctrl: {},
        alt: {}
    };

    function wkeypress (chr, key, callback) {
        if (typeof key === "function") {
            callback = key;
            key = null;
        }
        key = /^ctrl|shift$/.test(key) ? key : "chars";
        (_handlers[key][chr] = _handlers[key][chr] || []).push(callback);
    }

    function clbk(arr, scope, args) {
        arr.forEach(function (c) {
            c.apply(scope, args);
        });
    }

    window.addEventListener("keypress", function (e) {

        var charp = getChar(e);
        var callbacks = {};
        Object.keys(_handlers).forEach(function (c) {
            callbacks[c] = _handlers[c][charp];
        });

        if (e.ctrlKey) {
            clbk(callbacks.ctrl, this, [e]);
        } else if (e.shiftKey) {
            clbk(callbacks.shift, this, [e]);
        } else if (e.altKey) {
            clbk(callbacks.alt, this, [e]);
        } else {
            clbk(callbacks.chars, this, [e]);
        }
    });

    if (exports) {
        exports.module = wkeypress;
    } else {
        root.wkeypress = wkeypress;
    }
})(this);
<commit_msg>Export the function using module.exports.<commit_after>(function (root) {

    function getChar(e) {
        var keynum;

        if (window.event) {
            keynum = e.keyCode;
        } else if(e.which){
            keynum = e.which;
        }

        return keynum;
    }

    var _handlers = {
        chars: {},
        shift: {},
        ctrl: {},
        alt: {}
    };

    function wkeypress (chr, key, callback) {
        if (typeof key === "function") {
            callback = key;
            key = null;
        }
        key = /^ctrl|shift|alt$/.test(key) ? key : "chars";
        (_handlers[key][chr] = _handlers[key][chr] || []).push(callback);
    }

    function clbk(arr, scope, args) {
        if (arr.length) {
            args[0].preventDefault();
        }
        arr.forEach(function (c) {
            c.apply(scope, args);
        });
    }

    window.addEventListener("keydown", function (e) {

        var charp = getChar(e);
        var callbacks = {};
        Object.keys(_handlers).forEach(function (c) {
            callbacks[c] = _handlers[c][charp] || [];
        });

        if (e.ctrlKey) {
            clbk(callbacks.ctrl, this, [e]);
        } else if (e.shiftKey) {
            clbk(callbacks.shift, this, [e]);
        } else if (e.altKey) {
            clbk(callbacks.alt, this, [e]);
        } else {
            clbk(callbacks.chars, this, [e]);
        }

    });

    if (root) {
        root.exports = wkeypress;
    } else {
        root.wkeypress = wkeypress;
    }
})(module);
<|endoftext|>


<commit_before>import Ember from 'ember';
import Post from 'kowa/models/post';

// @todo Refactor that into a ContentEditionController Mixin with properties:
//  - model fields that are editable (eg: ["title, "body"])
//  - msg to display on save success (eg: "Post saved.")
//  - msg to display on save error (eg: "Failed to save post.")
//  - new record properties: (eg: Post.newRecordAttrs())
var PostEditController = Ember.ObjectController.extend({
  isDirty: function() {
    var model = this.get('model');
    return ((model.get('isNew')) ||
            (model.get('title') !== this.get('titleScratch')) ||
            (model.get('body')  !== this.get('bodyScratch')));
  }.property('titleScratch', 'bodyScratch', 'model.title', 'model.body', 'model.isNew'),

  nothingChanged: Ember.computed.not('isDirty'),

  setupEdition: function() {
    var model = this.get('model');

    this.set('titleScratch', model.get('title'));
    this.set('bodyScratch', model.get('body'));
  },

  actions: {
    savePost: function() {
      if (this.get('isDirty')) {
        // update model
        var model = this.get('model');
        model.set('title', this.get('titleScratch'));
        model.set('body', this.get('bodyScratch'));

        // persist on server
        var self = this;
        model.save().then(function (postSaved) {
          self.get('flashes').success('Post saved.');

          self.transitionToRoute('post', postSaved);
          return postSaved;
        }).catch(function () {
          self.get('flashes').danger('Failed to save post.');

          // rollback model
          if (model.get('isNew')) {
            model.setProperties(Post.newRecordAttrs());
          }
          else {
            model.rollback();
          }
        });
      }
      else {
        // @todo We should never reach that
        this.get('flashes').danger('Nothing to save.');
      }
    }
  }
});

export default PostEditController;
<commit_msg>Cleanup<commit_after>import Ember from 'ember';
import Post from 'kowa/models/post';

// @todo Refactor that into a ContentEditionController Mixin with properties:
//  - model fields that are editable (eg: ["title, "body"])
//  - msg to display on save success (eg: "Post saved.")
//  - msg to display on save error (eg: "Failed to save post.")
//  - new record properties: (eg: Post.newRecordAttrs())
var PostEditController = Ember.ObjectController.extend({
  isDirty: function() {
    var model = this.get('model');
    return ((model.get('isNew')) ||
            (model.get('title') !== this.get('titleScratch')) ||
            (model.get('body')  !== this.get('bodyScratch')));
  }.property('titleScratch', 'bodyScratch', 'model.title', 'model.body', 'model.isNew'),

  nothingChanged: Ember.computed.not('isDirty'),

  setupEdition: function() {
    var model = this.get('model');

    this.set('titleScratch', model.get('title'));
    this.set('bodyScratch', model.get('body'));
  },

  actions: {
    savePost: function() {
      if (!this.get('isDirty')) {
        // This should never happen
        return;
      }

      // update model
      var model = this.get('model');
      model.set('title', this.get('titleScratch'));
      model.set('body', this.get('bodyScratch'));

      // persist on server
      var self = this;
      model.save().then(function (postSaved) {
        self.get('flashes').success('Post saved.');

        self.transitionToRoute('post', postSaved);
        return postSaved;
      }).catch(function () {
        self.get('flashes').danger('Failed to save post.');

        // rollback model
        if (model.get('isNew')) {
          model.setProperties(Post.newRecordAttrs());
        }
        else {
          model.rollback();
        }
      });
    }
  }
});

export default PostEditController;
<|endoftext|>