added textbind example, improved & fixed syncing, RBTree handles ids correctly now, webrtc connector is quite reliable now
This commit is contained in:
		
							parent
							
								
									f9f8228db6
								
							
						
					
					
						commit
						f78dc52d7b
					
				@ -34,6 +34,8 @@
 | 
				
			|||||||
    "createUsers": true,
 | 
					    "createUsers": true,
 | 
				
			||||||
    "getRandomNumber": true,
 | 
					    "getRandomNumber": true,
 | 
				
			||||||
    "applyRandomTransactions": true,
 | 
					    "applyRandomTransactions": true,
 | 
				
			||||||
    "CustomType": true
 | 
					    "CustomType": true,
 | 
				
			||||||
 | 
					    "window": true,
 | 
				
			||||||
 | 
					    "document": true
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										20
									
								
								Examples/TextBind/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Examples/TextBind/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					  <meta charset=utf-8 />
 | 
				
			||||||
 | 
					  <title>Y Example</title>
 | 
				
			||||||
 | 
					  <script src="../../node_modules/simplewebrtc/simplewebrtc.bundle.js"></script>
 | 
				
			||||||
 | 
					  <script src="../../y.js"></script>
 | 
				
			||||||
 | 
					  <script src="./index.js"></script>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					<h1 contentEditable> yjs Tutorial</h1>
 | 
				
			||||||
 | 
					<p> Collaborative Json editing with <a href="https://github.com/rwth-acis/yjs/">yjs</a>
 | 
				
			||||||
 | 
					and XMPP Connector. </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<textarea style="width:80%;" rows=40 id="textfield"></textarea>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p> <a href="https://github.com/y-js/yjs/">yjs</a> is a Framework for Real-Time collaboration on arbitrary data types.
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										27
									
								
								Examples/TextBind/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Examples/TextBind/index.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					Y({
 | 
				
			||||||
 | 
					  db: {
 | 
				
			||||||
 | 
					    name: "Memory"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  connector: {
 | 
				
			||||||
 | 
					    name: "WebRTC",
 | 
				
			||||||
 | 
					    room: "mineeeeeee",
 | 
				
			||||||
 | 
					    debug: true
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}).then(function(yconfig){
 | 
				
			||||||
 | 
					  window.y = yconfig.root;
 | 
				
			||||||
 | 
					  window.yconfig = yconfig;
 | 
				
			||||||
 | 
					  var textarea = document.getElementById("textfield");
 | 
				
			||||||
 | 
					  yconfig.root.observe(function(events){
 | 
				
			||||||
 | 
					    for (var e in events) {
 | 
				
			||||||
 | 
					      var event = events[e];
 | 
				
			||||||
 | 
					      if (event.name === "text" && (event.type === "add" || event.type === "update")) {
 | 
				
			||||||
 | 
					        event.object.get(event.name).then(function(text){ //eslint-disable-line
 | 
				
			||||||
 | 
					          text.bind(textarea);
 | 
				
			||||||
 | 
					          window.ytext = text;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  yconfig.root.set("text", Y.TextBind);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -70,7 +70,8 @@ var options = minimist(process.argv.slice(2), {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var files = {
 | 
					var files = {
 | 
				
			||||||
  y: polyfills.concat(["src/y.js", "src/Connector.js", "src/OperationStore.js", "src/Struct.js", "src/**/*.js", "!src/**/*.spec.js"]),
 | 
					  y: polyfills.concat(["src/y.js", "src/Connector.js", "src/OperationStore.js", "src/Struct.js", "src/Utils.js",
 | 
				
			||||||
 | 
					    "src/OperationStores/RedBlackTree.js", "src/**/*.js", "!src/**/*.spec.js"]),
 | 
				
			||||||
  lint: ["src/**/*.js", "gulpfile.js"],
 | 
					  lint: ["src/**/*.js", "gulpfile.js"],
 | 
				
			||||||
  test: polyfills.concat([options.testfiles]),
 | 
					  test: polyfills.concat([options.testfiles]),
 | 
				
			||||||
  build_test: ["build_test/y.js"]
 | 
					  build_test: ["build_test/y.js"]
 | 
				
			||||||
 | 
				
			|||||||
@ -25,6 +25,7 @@ class AbstractConnector { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
    this.syncingClients = [];
 | 
					    this.syncingClients = [];
 | 
				
			||||||
    this.forwardToSyncingClients = (opts.forwardToSyncingClients === false) ? false : true;
 | 
					    this.forwardToSyncingClients = (opts.forwardToSyncingClients === false) ? false : true;
 | 
				
			||||||
    this.debug = opts.debug ? true : false;
 | 
					    this.debug = opts.debug ? true : false;
 | 
				
			||||||
 | 
					    this.broadcastedHB = false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  setUserId (userId) {
 | 
					  setUserId (userId) {
 | 
				
			||||||
    this.userId = userId;
 | 
					    this.userId = userId;
 | 
				
			||||||
@ -117,7 +118,8 @@ class AbstractConnector { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (this.debug) {
 | 
					    if (this.debug) {
 | 
				
			||||||
      console.log(`${sender} -> ${this.userId}: ${JSON.stringify(m)}`); //eslint-disable-line
 | 
					      console.log(`${sender} -> me: ${m.type}`);//eslint-disable-line
 | 
				
			||||||
 | 
					      console.dir(m); //eslint-disable-line
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (m.type === "sync step 1") {
 | 
					    if (m.type === "sync step 1") {
 | 
				
			||||||
      // TODO: make transaction, stream the ops
 | 
					      // TODO: make transaction, stream the ops
 | 
				
			||||||
@ -148,6 +150,7 @@ class AbstractConnector { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
      this.y.db.requestTransaction(function*(){
 | 
					      this.y.db.requestTransaction(function*(){
 | 
				
			||||||
        var ops = yield* this.getOperations(m.stateVector);
 | 
					        var ops = yield* this.getOperations(m.stateVector);
 | 
				
			||||||
        if (ops.length > 0) {
 | 
					        if (ops.length > 0) {
 | 
				
			||||||
 | 
					          conn.broadcastedHB = true;
 | 
				
			||||||
          conn.broadcast({
 | 
					          conn.broadcast({
 | 
				
			||||||
            type: "update",
 | 
					            type: "update",
 | 
				
			||||||
            ops: ops
 | 
					            ops: ops
 | 
				
			||||||
 | 
				
			|||||||
@ -12,12 +12,12 @@ class WebRTC extends AbstractConnector {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    var room = options.room;
 | 
					    var room = options.room;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // connect per default to our server
 | 
					    var webrtcOptions = {
 | 
				
			||||||
    if(options.url == null){
 | 
					      url: options.url || "https://yatta.ninja:8888",
 | 
				
			||||||
      options.url = "https://yatta.ninja:8888";
 | 
					      room: options.room
 | 
				
			||||||
    }
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var swr = new SimpleWebRTC(options); //eslint-disable-line no-undef
 | 
					    var swr = new SimpleWebRTC(webrtcOptions); //eslint-disable-line no-undef
 | 
				
			||||||
    this.swr = swr;
 | 
					    this.swr = swr;
 | 
				
			||||||
    var self = this;
 | 
					    var self = this;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -21,33 +21,20 @@ class AbstractTransaction { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
    this.store.initializedTypes[sid] = t;
 | 
					    this.store.initializedTypes[sid] = t;
 | 
				
			||||||
    return t;
 | 
					    return t;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  // returns false if operation is not expected.
 | 
					 | 
				
			||||||
  *addOperation (op) {
 | 
					 | 
				
			||||||
    var state = yield* this.getState(op.id[0]);
 | 
					 | 
				
			||||||
    if (op.id[1] === state.clock){
 | 
					 | 
				
			||||||
      state.clock++;
 | 
					 | 
				
			||||||
      yield* this.setState(state);
 | 
					 | 
				
			||||||
      this.os.add(op);
 | 
					 | 
				
			||||||
      yield* this.store.operationAdded(this, op);
 | 
					 | 
				
			||||||
      return true;
 | 
					 | 
				
			||||||
    } else if (op.id[1] < state.clock) {
 | 
					 | 
				
			||||||
      return false;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      throw new Error("Operations must arrive in order!");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  *applyCreatedOperations (ops) {
 | 
					  *applyCreatedOperations (ops) {
 | 
				
			||||||
    var send = [];
 | 
					    var send = [];
 | 
				
			||||||
    for (var i = 0; i < ops.length; i++) {
 | 
					    for (var i = 0; i < ops.length; i++) {
 | 
				
			||||||
      var op = ops[i];
 | 
					      var op = ops[i];
 | 
				
			||||||
      yield* Struct[op.struct].execute.call(this, op);
 | 
					      yield* this.store.tryExecute.call(this, op);
 | 
				
			||||||
      send.push(copyObject(Struct[op.struct].encode(op)));
 | 
					      send.push(copyObject(Struct[op.struct].encode(op)));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (this.store.y.connector.broadcastedHB){
 | 
				
			||||||
      this.store.y.connector.broadcast({
 | 
					      this.store.y.connector.broadcast({
 | 
				
			||||||
        type: "update",
 | 
					        type: "update",
 | 
				
			||||||
        ops: send
 | 
					        ops: send
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Listener = {
 | 
					type Listener = {
 | 
				
			||||||
@ -79,10 +66,23 @@ class AbstractOperationStore { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
    // TODO: Use ES7 Weak Maps. This way types that are no longer user,
 | 
					    // TODO: Use ES7 Weak Maps. This way types that are no longer user,
 | 
				
			||||||
    // wont be kept in memory.
 | 
					    // wont be kept in memory.
 | 
				
			||||||
    this.initializedTypes = {};
 | 
					    this.initializedTypes = {};
 | 
				
			||||||
 | 
					    this.whenUserIdSetListener = null;
 | 
				
			||||||
 | 
					    this.waitingOperations = new RBTree();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  setUserId (userId) {
 | 
					  setUserId (userId) {
 | 
				
			||||||
    this.userId = userId;
 | 
					    this.userId = userId;
 | 
				
			||||||
    this.opClock = 0;
 | 
					    this.opClock = 0;
 | 
				
			||||||
 | 
					    if (this.whenUserIdSetListener != null) {
 | 
				
			||||||
 | 
					      this.whenUserIdSetListener();
 | 
				
			||||||
 | 
					      this.whenUserIdSetListener = null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  whenUserIdSet (f) {
 | 
				
			||||||
 | 
					    if (this.userId != null) {
 | 
				
			||||||
 | 
					      f();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this.whenUserIdSetListener = f;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  getNextOpId () {
 | 
					  getNextOpId () {
 | 
				
			||||||
    if (this.userId == null) {
 | 
					    if (this.userId == null) {
 | 
				
			||||||
@ -140,7 +140,7 @@ class AbstractOperationStore { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      for (let key in exeNow) {
 | 
					      for (let key in exeNow) {
 | 
				
			||||||
        let o = exeNow[key].op;
 | 
					        let o = exeNow[key].op;
 | 
				
			||||||
        yield* Struct[o.struct].execute.call(this, o);
 | 
					        yield* store.tryExecute.call(this, o);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      for (var sid in ls){
 | 
					      for (var sid in ls){
 | 
				
			||||||
@ -153,13 +153,37 @@ class AbstractOperationStore { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
            let listener = l[key];
 | 
					            let listener = l[key];
 | 
				
			||||||
            let o = listener.op;
 | 
					            let o = listener.op;
 | 
				
			||||||
            if (--listener.missing === 0){
 | 
					            if (--listener.missing === 0){
 | 
				
			||||||
              yield* Struct[o.struct].execute.call(this, o);
 | 
					              yield* store.tryExecute.call(this, o);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  *tryExecute (op) {
 | 
				
			||||||
 | 
					    if (op.struct === "Delete") {
 | 
				
			||||||
 | 
					      yield* Struct.Delete.execute.call(this, op);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      while (op != null) {
 | 
				
			||||||
 | 
					        var state = yield* this.getState(op.id[0]);
 | 
				
			||||||
 | 
					        if (op.id[1] === state.clock){
 | 
				
			||||||
 | 
					          state.clock++;
 | 
				
			||||||
 | 
					          yield* this.setState.call(this, state);
 | 
				
			||||||
 | 
					          yield* Struct[op.struct].execute.call(this, op);
 | 
				
			||||||
 | 
					          yield* this.addOperation(op);
 | 
				
			||||||
 | 
					          yield* this.store.operationAdded(this, op);
 | 
				
			||||||
 | 
					          // find next operation to execute
 | 
				
			||||||
 | 
					          op = this.store.waitingOperations.find([op.id[0], state.clock]);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          if (op.id[1] > state.clock) {
 | 
				
			||||||
 | 
					            // has to be executed at some point later
 | 
				
			||||||
 | 
					            this.store.waitingOperations.add(op);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          op = null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  // called by a transaction when an operation is added
 | 
					  // called by a transaction when an operation is added
 | 
				
			||||||
  *operationAdded (transaction, op) {
 | 
					  *operationAdded (transaction, op) {
 | 
				
			||||||
    var sid = JSON.stringify(op.id);
 | 
					    var sid = JSON.stringify(op.id);
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,10 @@
 | 
				
			|||||||
if(typeof window !== "undefined"){
 | 
					if(typeof window !== "undefined"){
 | 
				
			||||||
  jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
 | 
					  jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
 | 
				
			||||||
  describe("IndexedDB", function() {
 | 
					  describe("IndexedDB", function() {
 | 
				
			||||||
    var ob = new Y.IndexedDB(null, {namespace: "Test"});
 | 
					    var ob;
 | 
				
			||||||
 | 
					    beforeAll(function(){
 | 
				
			||||||
 | 
					      ob = new Y.IndexedDB(null, {namespace: "Test"});
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("can add and get operation", function(done) {
 | 
					    it("can add and get operation", function(done) {
 | 
				
			||||||
      ob.requestTransaction(function*(){
 | 
					      ob.requestTransaction(function*(){
 | 
				
			||||||
 | 
				
			|||||||
@ -33,6 +33,9 @@ Y.Memory = (function(){ //eslint-disable-line no-unused-vars
 | 
				
			|||||||
      n.val = op;
 | 
					      n.val = op;
 | 
				
			||||||
      return op;
 | 
					      return op;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    *addOperation (op) {
 | 
				
			||||||
 | 
					      this.os.add(op);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    *getOperation (id) {
 | 
					    *getOperation (id) {
 | 
				
			||||||
      if (id == null) {
 | 
					      if (id == null) {
 | 
				
			||||||
        throw new Error("You must define id!");
 | 
					        throw new Error("You must define id!");
 | 
				
			||||||
@ -97,21 +100,18 @@ Y.Memory = (function(){ //eslint-disable-line no-unused-vars
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    *makeOperationReady (ss, op) {
 | 
					    *makeOperationReady (ss, op) {
 | 
				
			||||||
      // instead of ss, you could use currSS (a ss that increments when you add an operation)
 | 
					      // instead of ss, you could use currSS (a ss that increments when you add an operation)
 | 
				
			||||||
      if (op.right == null) {
 | 
					 | 
				
			||||||
        return op;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      var clock;
 | 
					      var clock;
 | 
				
			||||||
      var o = op;
 | 
					      var o = op;
 | 
				
			||||||
      while (o.right != null){
 | 
					      while (o.right != null){
 | 
				
			||||||
        // while unknown, go to the right
 | 
					        // while unknown, go to the right
 | 
				
			||||||
        o = yield* this.getOperation(o.right);
 | 
					        clock = ss[o.right[0]];
 | 
				
			||||||
        clock = ss[o.id[0]];
 | 
					        if (clock != null && o.right[1] < clock) {
 | 
				
			||||||
        if (clock != null && o.id[1] < clock) {
 | 
					 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        o = yield* this.getOperation(o.right);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      op = copyObject(op);
 | 
					      op = copyObject(op);
 | 
				
			||||||
      op.right = (o == null) ? null : o.id;
 | 
					      op.right = o.right;
 | 
				
			||||||
      return op;
 | 
					      return op;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					function smaller (a, b) {
 | 
				
			||||||
 | 
					  return a[0] < b[0] || (a[0] === b[0] && a[1] < b[1]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class N {
 | 
					class N {
 | 
				
			||||||
  // A created node is always red!
 | 
					  // A created node is always red!
 | 
				
			||||||
  constructor (val) {
 | 
					  constructor (val) {
 | 
				
			||||||
@ -106,6 +110,7 @@ class N {
 | 
				
			|||||||
class RBTree { //eslint-disable-line no-unused-vars
 | 
					class RBTree { //eslint-disable-line no-unused-vars
 | 
				
			||||||
  constructor () {
 | 
					  constructor () {
 | 
				
			||||||
    this.root = null;
 | 
					    this.root = null;
 | 
				
			||||||
 | 
					    this.length = 0;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  findNodeWithLowerBound (from) {
 | 
					  findNodeWithLowerBound (from) {
 | 
				
			||||||
    var o = this.root;
 | 
					    var o = this.root;
 | 
				
			||||||
@ -113,11 +118,11 @@ class RBTree { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      while (true) {
 | 
					      while (true) {
 | 
				
			||||||
        if ((from === null || from < o.val.id) && o.left !== null) {
 | 
					        if ((from === null || smaller(from, o.val.id)) && o.left !== null) {
 | 
				
			||||||
          // o is included in the bound
 | 
					          // o is included in the bound
 | 
				
			||||||
          // try to find an element that is closer to the bound
 | 
					          // try to find an element that is closer to the bound
 | 
				
			||||||
          o = o.left;
 | 
					          o = o.left;
 | 
				
			||||||
        } else if (o.val.id < from) {
 | 
					        } else if (smaller(o.val.id, from)) {
 | 
				
			||||||
          // o is not within the bound, maybe one of the right elements is..
 | 
					          // o is not within the bound, maybe one of the right elements is..
 | 
				
			||||||
          if (o.right !== null) {
 | 
					          if (o.right !== null) {
 | 
				
			||||||
            o = o.right;
 | 
					            o = o.right;
 | 
				
			||||||
@ -134,7 +139,7 @@ class RBTree { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  iterate (from, to, f) {
 | 
					  iterate (from, to, f) {
 | 
				
			||||||
    var o = this.findNodeWithLowerBound(from);
 | 
					    var o = this.findNodeWithLowerBound(from);
 | 
				
			||||||
    while ( o !== null && (to === null || o.val.id <= to) ) {
 | 
					    while ( o !== null && (to === null || smaller(o.val.id, to) || compareIds(o.val.id, to)) ) {
 | 
				
			||||||
      f(o.val);
 | 
					      f(o.val);
 | 
				
			||||||
      o = o.next();
 | 
					      o = o.next();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -152,9 +157,9 @@ class RBTree { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
        if (o === null) {
 | 
					        if (o === null) {
 | 
				
			||||||
          return false;
 | 
					          return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (id < o.val.id) {
 | 
					        if (smaller(id, o.val.id)) {
 | 
				
			||||||
          o = o.left;
 | 
					          o = o.left;
 | 
				
			||||||
        } else if (o.val.id < id) {
 | 
					        } else if (smaller(o.val.id, id)) {
 | 
				
			||||||
          o = o.right;
 | 
					          o = o.right;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          return o;
 | 
					          return o;
 | 
				
			||||||
@ -164,6 +169,10 @@ class RBTree { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  delete (id) {
 | 
					  delete (id) {
 | 
				
			||||||
    var d = this.findNode(id);
 | 
					    var d = this.findNode(id);
 | 
				
			||||||
 | 
					    if (d == null) {
 | 
				
			||||||
 | 
					      throw new Error("Element does not exist!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.length--;
 | 
				
			||||||
    if (d.left !== null && d.right !== null) {
 | 
					    if (d.left !== null && d.right !== null) {
 | 
				
			||||||
      // switch d with the greates element in the left subtree.
 | 
					      // switch d with the greates element in the left subtree.
 | 
				
			||||||
      // o should have at most one child.
 | 
					      // o should have at most one child.
 | 
				
			||||||
@ -302,14 +311,14 @@ class RBTree { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
    if (this.root !== null) {
 | 
					    if (this.root !== null) {
 | 
				
			||||||
      var p = this.root; // p abbrev. parent
 | 
					      var p = this.root; // p abbrev. parent
 | 
				
			||||||
      while (true) {
 | 
					      while (true) {
 | 
				
			||||||
        if (node.val.id < p.val.id) {
 | 
					        if (smaller(node.val.id, p.val.id)) {
 | 
				
			||||||
          if (p.left === null) {
 | 
					          if (p.left === null) {
 | 
				
			||||||
            p.left = node;
 | 
					            p.left = node;
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            p = p.left;
 | 
					            p = p.left;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else if (p.val.id < node.val.id) {
 | 
					        } else if (smaller(p.val.id, node.val.id)) {
 | 
				
			||||||
          if (p.right === null) {
 | 
					          if (p.right === null) {
 | 
				
			||||||
            p.right = node;
 | 
					            p.right = node;
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
@ -324,6 +333,7 @@ class RBTree { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      this.root = node;
 | 
					      this.root = node;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    this.length++;
 | 
				
			||||||
    this.root.blacken();
 | 
					    this.root.blacken();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  _fixInsert (n) {
 | 
					  _fixInsert (n) {
 | 
				
			||||||
 | 
				
			|||||||
@ -184,22 +184,14 @@ var Struct = {
 | 
				
			|||||||
      var right = null;
 | 
					      var right = null;
 | 
				
			||||||
      parent = parent || (yield* this.getOperation(op.parent));
 | 
					      parent = parent || (yield* this.getOperation(op.parent));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // NOTE: You you have to call addOperation before you set any other operation!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // reconnect left and set right of op
 | 
					      // reconnect left and set right of op
 | 
				
			||||||
      if (op.left != null) {
 | 
					      if (op.left != null) {
 | 
				
			||||||
        left = yield* this.getOperation(op.left);
 | 
					        left = yield* this.getOperation(op.left);
 | 
				
			||||||
        op.right = left.right;
 | 
					        op.right = left.right;
 | 
				
			||||||
        if ((yield* this.addOperation(op)) === false) { // add here
 | 
					 | 
				
			||||||
          return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        left.right = op.id;
 | 
					        left.right = op.id;
 | 
				
			||||||
        yield* this.setOperation(left);
 | 
					        yield* this.setOperation(left);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        op.right = op.parentSub ? (parent.map[op.parentSub] || null) : parent.start;
 | 
					        op.right = op.parentSub ? (parent.map[op.parentSub] || null) : parent.start;
 | 
				
			||||||
        if ((yield* this.addOperation(op)) === false) { // or here
 | 
					 | 
				
			||||||
          return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      // reconnect right
 | 
					      // reconnect right
 | 
				
			||||||
      if (op.right != null) {
 | 
					      if (op.right != null) {
 | 
				
			||||||
@ -260,9 +252,6 @@ var Struct = {
 | 
				
			|||||||
    execute: function* (op) {
 | 
					    execute: function* (op) {
 | 
				
			||||||
      op.start = null;
 | 
					      op.start = null;
 | 
				
			||||||
      op.end = null;
 | 
					      op.end = null;
 | 
				
			||||||
      if ((yield* this.addOperation(op)) === false) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    ref: function* (op : Op, pos : number) : Insert {
 | 
					    ref: function* (op : Op, pos : number) : Insert {
 | 
				
			||||||
      if (op.start == null) {
 | 
					      if (op.start == null) {
 | 
				
			||||||
@ -324,10 +313,7 @@ var Struct = {
 | 
				
			|||||||
      */
 | 
					      */
 | 
				
			||||||
      return [];
 | 
					      return [];
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    execute: function* (op) {
 | 
					    execute: function* () {
 | 
				
			||||||
      if ((yield* this.addOperation(op)) === false) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    get: function* (op, name) {
 | 
					    get: function* (op, name) {
 | 
				
			||||||
      var oid = op.map[name];
 | 
					      var oid = op.map[name];
 | 
				
			||||||
@ -347,3 +333,4 @@ var Struct = {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					Y.Struct = Struct;
 | 
				
			||||||
 | 
				
			|||||||
@ -149,6 +149,51 @@
 | 
				
			|||||||
    observe (f) {
 | 
					    observe (f) {
 | 
				
			||||||
      this.eventHandler.addUserEventListener(f);
 | 
					      this.eventHandler.addUserEventListener(f);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    unobserve (f) {
 | 
				
			||||||
 | 
					      this.eventHandler.removeUserEventListener(f);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    observePath (path, f) {
 | 
				
			||||||
 | 
					      var self = this;
 | 
				
			||||||
 | 
					      if (path.length === 0) {
 | 
				
			||||||
 | 
					        this.observe(f);
 | 
				
			||||||
 | 
					        return Promise.resolve(function(){
 | 
				
			||||||
 | 
					          self.unobserve(f);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        var deleteChildObservers;
 | 
				
			||||||
 | 
					        var resetObserverPath = function(){
 | 
				
			||||||
 | 
					          var promise = self.get(path[0]);
 | 
				
			||||||
 | 
					          if (!promise instanceof Promise) {
 | 
				
			||||||
 | 
					            // its either not defined or a premitive value
 | 
				
			||||||
 | 
					            promise = self.set(path[0], Y.Map);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return promise.then(function(map){
 | 
				
			||||||
 | 
					            return map.observePath(path.slice(1), f);
 | 
				
			||||||
 | 
					          }).then(function(_deleteChildObservers){
 | 
				
			||||||
 | 
					            deleteChildObservers = _deleteChildObservers;
 | 
				
			||||||
 | 
					            return Promise.resolve();
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        var observer = function(events){
 | 
				
			||||||
 | 
					          for (var e in events) {
 | 
				
			||||||
 | 
					            var event = events[e];
 | 
				
			||||||
 | 
					            if (event.name === path[0]) {
 | 
				
			||||||
 | 
					              deleteChildObservers();
 | 
				
			||||||
 | 
					              if (event.type === "add" || event.type === "update") {
 | 
				
			||||||
 | 
					                resetObserverPath();
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        self.observe(observer);
 | 
				
			||||||
 | 
					        return resetObserverPath().then(
 | 
				
			||||||
 | 
					          Promise.resolve(function(){
 | 
				
			||||||
 | 
					            deleteChildObservers();
 | 
				
			||||||
 | 
					            self.unobserve(observer);
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    *_changed (transaction, op) {
 | 
					    *_changed (transaction, op) {
 | 
				
			||||||
      if (op.struct === "Delete") {
 | 
					      if (op.struct === "Delete") {
 | 
				
			||||||
        op.key = (yield* transaction.getOperation(op.target)).parentSub;
 | 
					        op.key = (yield* transaction.getOperation(op.target)).parentSub;
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,287 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					(function(){
 | 
				
			||||||
 | 
					  class YTextBind extends Y.Array.class {
 | 
				
			||||||
 | 
					    constructor (os, _model, idArray, valArray) {
 | 
				
			||||||
 | 
					      super(os, _model, idArray, valArray);
 | 
				
			||||||
 | 
					      this.textfields = [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    toString () {
 | 
				
			||||||
 | 
					      return this.valArray.join("");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    insert (pos, content) {
 | 
				
			||||||
 | 
					      super(pos, content.split(""));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    bind (textfield, domRoot) {
 | 
				
			||||||
 | 
					        domRoot = domRoot || window; //eslint-disable-line
 | 
				
			||||||
 | 
					        if (domRoot.getSelection == null) {
 | 
				
			||||||
 | 
					          domRoot = window;//eslint-disable-line
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // don't duplicate!
 | 
				
			||||||
 | 
					        for (var t in this.textfields) {
 | 
				
			||||||
 | 
					          if (this.textfields[t] === textfield) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        var creatorToken = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var word = this;
 | 
				
			||||||
 | 
					        textfield.value = this.toString();
 | 
				
			||||||
 | 
					        this.textfields.push(textfield);
 | 
				
			||||||
 | 
					        var createRange, writeRange, writeContent;
 | 
				
			||||||
 | 
					        if(textfield.selectionStart != null && textfield.setSelectionRange != null) {
 | 
				
			||||||
 | 
					          createRange = function (fix) {
 | 
				
			||||||
 | 
					            var left = textfield.selectionStart;
 | 
				
			||||||
 | 
					            var right = textfield.selectionEnd;
 | 
				
			||||||
 | 
					            if (fix != null) {
 | 
				
			||||||
 | 
					              left = fix(left);
 | 
				
			||||||
 | 
					              right = fix(right);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					              left: left,
 | 
				
			||||||
 | 
					              right: right
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					          writeRange = function (range) {
 | 
				
			||||||
 | 
					            writeContent(word.toString());
 | 
				
			||||||
 | 
					            textfield.setSelectionRange(range.left, range.right);
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					          writeContent = function (content){
 | 
				
			||||||
 | 
					            textfield.value = content;
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          createRange = function (fix) {
 | 
				
			||||||
 | 
					            var range = {};
 | 
				
			||||||
 | 
					            var s = domRoot.getSelection();
 | 
				
			||||||
 | 
					            var clength = textfield.textContent.length;
 | 
				
			||||||
 | 
					            range.left = Math.min(s.anchorOffset, clength);
 | 
				
			||||||
 | 
					            range.right = Math.min(s.focusOffset, clength);
 | 
				
			||||||
 | 
					            if(fix != null){
 | 
				
			||||||
 | 
					              range.left = fix(range.left);
 | 
				
			||||||
 | 
					              range.right = fix(range.right);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            var editedElement = s.focusNode;
 | 
				
			||||||
 | 
					            if(editedElement === textfield || editedElement === textfield.childNodes[0]){
 | 
				
			||||||
 | 
					              range.isReal = true;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              range.isReal = false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return range;
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          writeRange = function (range) {
 | 
				
			||||||
 | 
					            writeContent(word.val());
 | 
				
			||||||
 | 
					            var textnode = textfield.childNodes[0];
 | 
				
			||||||
 | 
					            if(range.isReal && textnode != null) {
 | 
				
			||||||
 | 
					              if(range.left < 0){
 | 
				
			||||||
 | 
					                range.left = 0;
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              range.right = Math.max(range.left, range.right);
 | 
				
			||||||
 | 
					              if (range.right > textnode.length) {
 | 
				
			||||||
 | 
					                range.right = textnode.length;
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              range.left = Math.min(range.left, range.right);
 | 
				
			||||||
 | 
					              var r = document.createRange(); //eslint-disable-line
 | 
				
			||||||
 | 
					              r.setStart(textnode, range.left);
 | 
				
			||||||
 | 
					              r.setEnd(textnode, range.right);
 | 
				
			||||||
 | 
					              var s = window.getSelection(); //eslint-disable-line
 | 
				
			||||||
 | 
					              s.removeAllRanges();
 | 
				
			||||||
 | 
					              s.addRange(r);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					          writeContent = function (content) {
 | 
				
			||||||
 | 
					            var contentArray = content.replace(new RegExp("\n", 'g')," ").split(" ");//eslint-disable-line
 | 
				
			||||||
 | 
					            textfield.innerText = "";
 | 
				
			||||||
 | 
					            for(var i in contentArray){
 | 
				
			||||||
 | 
					              var c = contentArray[i];
 | 
				
			||||||
 | 
					              textfield.innerText += c;
 | 
				
			||||||
 | 
					              if(i !== contentArray.length - 1){
 | 
				
			||||||
 | 
					                textfield.innerHTML += " ";
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        writeContent(this.toString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.observe(function (events) {
 | 
				
			||||||
 | 
					          for(var e in events) {
 | 
				
			||||||
 | 
					            var event = events[e];
 | 
				
			||||||
 | 
					            if (!creatorToken) {
 | 
				
			||||||
 | 
					              var oPos, fix;
 | 
				
			||||||
 | 
					              if( event.type === "insert") {
 | 
				
			||||||
 | 
					                oPos = event.index;
 | 
				
			||||||
 | 
					                fix = function (cursor) {//eslint-disable-line
 | 
				
			||||||
 | 
					                  if (cursor <= oPos) {
 | 
				
			||||||
 | 
					                    return cursor;
 | 
				
			||||||
 | 
					                  } else {
 | 
				
			||||||
 | 
					                    cursor += 1;
 | 
				
			||||||
 | 
					                    return cursor;
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                var r = createRange(fix);
 | 
				
			||||||
 | 
					                writeRange(r);
 | 
				
			||||||
 | 
					              } else if (event.type === "delete") {
 | 
				
			||||||
 | 
					                oPos = event.index;
 | 
				
			||||||
 | 
					                fix = function (cursor){//eslint-disable-line
 | 
				
			||||||
 | 
					                  if (cursor < oPos) {
 | 
				
			||||||
 | 
					                    return cursor;
 | 
				
			||||||
 | 
					                  } else {
 | 
				
			||||||
 | 
					                    cursor -= 1;
 | 
				
			||||||
 | 
					                    return cursor;
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                r = createRange(fix);
 | 
				
			||||||
 | 
					                writeRange(r);
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        // consume all text-insert changes.
 | 
				
			||||||
 | 
					        textfield.onkeypress = function (event) {
 | 
				
			||||||
 | 
					          if (word.is_deleted) {
 | 
				
			||||||
 | 
					            // if word is deleted, do not do anything ever again
 | 
				
			||||||
 | 
					            textfield.onkeypress = null;
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          creatorToken = true;
 | 
				
			||||||
 | 
					          var char;
 | 
				
			||||||
 | 
					          if (event.keyCode === 13) {
 | 
				
			||||||
 | 
					            char = "\n";
 | 
				
			||||||
 | 
					          } else if (event.key != null) {
 | 
				
			||||||
 | 
					            if (event.charCode === 32) {
 | 
				
			||||||
 | 
					              char = " ";
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              char = event.key;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            char = window.String.fromCharCode(event.keyCode); //eslint-disable-line
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          if (char.length > 1) {
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					          } else if (char.length > 0) {
 | 
				
			||||||
 | 
					            var r = createRange();
 | 
				
			||||||
 | 
					            var pos = Math.min(r.left, r.right);
 | 
				
			||||||
 | 
					            var diff = Math.abs(r.right - r.left);
 | 
				
			||||||
 | 
					            word.delete(pos, diff);
 | 
				
			||||||
 | 
					            word.insert(pos, char);
 | 
				
			||||||
 | 
					            r.left = pos + char.length;
 | 
				
			||||||
 | 
					            r.right = r.left;
 | 
				
			||||||
 | 
					            writeRange(r);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          event.preventDefault();
 | 
				
			||||||
 | 
					          creatorToken = false;
 | 
				
			||||||
 | 
					          return false;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        textfield.onpaste = function (event) {
 | 
				
			||||||
 | 
					          if (word.is_deleted) {
 | 
				
			||||||
 | 
					            // if word is deleted, do not do anything ever again
 | 
				
			||||||
 | 
					            textfield.onpaste = null;
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          event.preventDefault();
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        textfield.oncut = function (event) {
 | 
				
			||||||
 | 
					          if (word.is_deleted) {
 | 
				
			||||||
 | 
					            // if word is deleted, do not do anything ever again
 | 
				
			||||||
 | 
					            textfield.oncut = null;
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          event.preventDefault();
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        // consume deletes. Note that
 | 
				
			||||||
 | 
					        //   chrome: won't consume deletions on keypress event.
 | 
				
			||||||
 | 
					        //   keyCode is deprecated. BUT: I don't see another way.
 | 
				
			||||||
 | 
					        //     since event.key is not implemented in the current version of chrome.
 | 
				
			||||||
 | 
					        //     Every browser supports keyCode. Let's stick with it for now..
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        textfield.onkeydown = function (event) {
 | 
				
			||||||
 | 
					          creatorToken = true;
 | 
				
			||||||
 | 
					          if (word.is_deleted) {
 | 
				
			||||||
 | 
					            // if word is deleted, do not do anything ever again
 | 
				
			||||||
 | 
					            textfield.onkeydown = null;
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          var r = createRange();
 | 
				
			||||||
 | 
					          var pos = Math.min(r.left, r.right, word.toString().length);
 | 
				
			||||||
 | 
					          var diff = Math.abs(r.left - r.right);
 | 
				
			||||||
 | 
					          if (event.keyCode != null && event.keyCode === 8) { // Backspace
 | 
				
			||||||
 | 
					            if (diff > 0) {
 | 
				
			||||||
 | 
					              word.delete(pos, diff);
 | 
				
			||||||
 | 
					              r.left = pos;
 | 
				
			||||||
 | 
					              r.right = pos;
 | 
				
			||||||
 | 
					              writeRange(r);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              if (event.ctrlKey != null && event.ctrlKey) {
 | 
				
			||||||
 | 
					                var val = word.toString();
 | 
				
			||||||
 | 
					                var newPos = pos;
 | 
				
			||||||
 | 
					                var delLength = 0;
 | 
				
			||||||
 | 
					                if (pos > 0) {
 | 
				
			||||||
 | 
					                  newPos--;
 | 
				
			||||||
 | 
					                  delLength++;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                while (newPos > 0 && val[newPos] !== " " && val[newPos] !== "\n") {
 | 
				
			||||||
 | 
					                  newPos--;
 | 
				
			||||||
 | 
					                  delLength++;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                word.delete(newPos, pos - newPos);
 | 
				
			||||||
 | 
					                r.left = newPos;
 | 
				
			||||||
 | 
					                r.right = newPos;
 | 
				
			||||||
 | 
					                writeRange(r);
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                if (pos > 0) {
 | 
				
			||||||
 | 
					                  word.delete(pos - 1, 1);
 | 
				
			||||||
 | 
					                  r.left = pos - 1;
 | 
				
			||||||
 | 
					                  r.right = pos - 1;
 | 
				
			||||||
 | 
					                  writeRange(r);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            event.preventDefault();
 | 
				
			||||||
 | 
					            creatorToken = false;
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					          } else if (event.keyCode != null && event.keyCode === 46) { // Delete
 | 
				
			||||||
 | 
					            if (diff > 0) {
 | 
				
			||||||
 | 
					              word.delete(pos, diff);
 | 
				
			||||||
 | 
					              r.left = pos;
 | 
				
			||||||
 | 
					              r.right = pos;
 | 
				
			||||||
 | 
					              writeRange(r);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              word.delete(pos, 1);
 | 
				
			||||||
 | 
					              r.left = pos;
 | 
				
			||||||
 | 
					              r.right = pos;
 | 
				
			||||||
 | 
					              writeRange(r);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            event.preventDefault();
 | 
				
			||||||
 | 
					            creatorToken = false;
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            creatorToken = false;
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  Y.TextBind = new CustomType({
 | 
				
			||||||
 | 
					    class: YTextBind,
 | 
				
			||||||
 | 
					    createType: function* YTextBindCreator () {
 | 
				
			||||||
 | 
					      var model = {
 | 
				
			||||||
 | 
					        start: null,
 | 
				
			||||||
 | 
					        end: null,
 | 
				
			||||||
 | 
					        struct: "List",
 | 
				
			||||||
 | 
					        type: "TextBind",
 | 
				
			||||||
 | 
					        id: this.store.getNextOpId()
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      yield* this.applyCreatedOperations([model]);
 | 
				
			||||||
 | 
					      return yield* this.createType(model);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    initType: function* YTextBindInitializer(os, model){
 | 
				
			||||||
 | 
					      var valArray = [];
 | 
				
			||||||
 | 
					      var idArray = yield* Y.Struct.List.map.call(this, model, function(c){
 | 
				
			||||||
 | 
					        valArray.push(c.content);
 | 
				
			||||||
 | 
					        return JSON.stringify(c.id);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return new YTextBind(os, model.id, idArray, valArray);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					})();
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/y.js
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/y.js
									
									
									
									
									
								
							@ -2,8 +2,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function Y (opts) {
 | 
					function Y (opts) {
 | 
				
			||||||
  var def = Promise.defer();
 | 
					  var def = Promise.defer();
 | 
				
			||||||
  new YConfig(opts, function(config){ //eslint-disable-line
 | 
					  new YConfig(opts, function(yconfig){ //eslint-disable-line
 | 
				
			||||||
    def.resolve(config);
 | 
					    yconfig.db.whenUserIdSet(function(){
 | 
				
			||||||
 | 
					      def.resolve(yconfig);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  return def.promise;
 | 
					  return def.promise;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -37,7 +39,3 @@ class YConfig { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
Y.AbstractTransaction = AbstractTransaction;
 | 
					 | 
				
			||||||
Y.AbstractOperationStore = AbstractOperationStore;
 | 
					 | 
				
			||||||
Y.Struct = Struct;
 | 
					 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user