added textbind example, improved & fixed syncing, RBTree handles ids correctly now, webrtc connector is quite reliable now
This commit is contained in:
@@ -149,6 +149,51 @@
|
||||
observe (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) {
|
||||
if (op.struct === "Delete") {
|
||||
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);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user