Skip to content

Instantly share code, notes, and snippets.

@josephg
Created May 15, 2011 04:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save josephg/972894 to your computer and use it in GitHub Desktop.
Save josephg/972894 to your computer and use it in GitHub Desktop.
Sarah's ot code
<!DOCTYPE HTML>
<html>
<head>
<title>Wave textbox test page</title>
<meta charset="utf-8">
<style>
body {
width: 800px;
margin: auto;
}
textarea {
height: 400px;
width: 800px;
}
</style>
</head>
<body>
<h2>Testing, testing, 1, 2, 3...</h3>
<h4>Example textarea:</h4>
<textarea id="whatnot-textarea"></textarea>
<script type="text/javascript" src="http://192.168.0.51:8000/socket.io/socket.io.js"></script>
<script type="text/javascript" src="http://192.168.0.51/~josephg/webclient.js"></script>
<script>
var doc;
function setUp() {
var connection1 = new whatnot.Connection("192.168.0.51", 8000);
connection1.getOrCreate("sarahtest", 'text', function (doc, error) {
getOps(doc, "whatnot-textarea");
});
}
function getOps(doc, testAreaId) {
var textElem = document.getElementById(testAreaId), //the element containing the text being edited
selectedText = "", //records the selected text
selectedLen = 0; //records the length of selected text
sub = false;
textElem.spellcheck = false;
textElem.value = doc.snapshot;
doc.onChanged(update);
function update(op) {
var cursorPos = getCursor();
getSelected();
var selecEnd = document.activeElement.selectionEnd;
for (var x=0; x<op.length; x++) {
if (op[x].i) {
processInput(op[x].i, op[x].p, 0);
console.log("Op applied");
console.log(op);
if (cursorPos >= op[x].p) { //cursor & selection are after inserted text
cursorPos = cursorPos + op[x].i.length;
} else if (cursorPos < op[x].p && cursorPos + selectedLen > op[x].p) { //selection over area where insert applied
selectedLen = selectedLen + op[x].i.length;
}
} else if (op[x].d) {
textElem.value = textElem.value.substring(0, op[x].p) + textElem.value.substring(op[x].d.length + op[x].p);
console.log("Op applied");
console.log(op);
if (selecEnd <= op[x].p) { //cursor & selection are before deleted text: no change required
} else if (cursorPos >= op[x].p + op[x].d.length) { //cursor & selection are after deleted text
cursorPos = cursorPos - op[x].d.length;
} else if (cursorPos > op[x].p && cursorPos <= op[x].p + op[x].d.length && selecEnd > op[x].p + op[x].d.length) {
//selection overlaps end of deleted text
selectedLen = selectedLen - (op[x].p + op[x].d.length - cursorPos);
cursorPos = op[x].p;
} else if (selecEnd > op[x].p && selecEnd < op[x].p + op[x].d.length && cursorPos <= op[x].p) {
//selection overlaps start of deleted text
selectedLen = op[x].p - cursorPos;
} else if (cursorPos <= op[x].p && selecEnd >= op[x].p + op[x].d.length) { //selection overlaps all of deleted text
selectedLen = selectedLen - op[x].d.length;
} else if (cursorPos >= op[x].p && selecEnd <= op[x].p + op[x].d.length) { //selection entirely contained in deleted text
cursorPos = op[x].p;
selectedLen = 0;
}
} else {
console.error("Invalid op: neither insert nor delete");
}
}
selecEnd = cursorPos + selectedLen;
setCursor(cursorPos, selecEnd);
}
//set up event listeners...
textElem.addEventListener("textInput", getInputText, false);
textElem.addEventListener("keydown", onKeyDown, false);
textElem.addEventListener("keyup", domChanged, false);
textElem.addEventListener("select", getSelected, false);
textElem.addEventListener("cut", doCut, false);
textElem.addEventListener("paste", doPaste, false);
textElem.addEventListener("copy", doCopy, false);
//handler function: on a textInput event, records a string of input text into insertText
function getInputText(event) {
getSelected();
event.preventDefault();
var insertText = event.data;
var cursorPos = getCursor();
processInput(insertText, cursorPos, selectedLen);
setCursor(cursorPos + insertText.length, cursorPos + insertText.length);
if (selectedText) {
sendCombinedOp(insertText, cursorPos);
} else {
sendInsertOp(insertText, cursorPos);
}
}
function processInput(insertText, cursorPos, offset) {
if (insertText.length > 1) { //this deals with Windows newlines
insertText = insertText.replace(/\r/g, "");
}
textElem.value = textElem.value.substring(0, cursorPos) + insertText + textElem.value.substring(cursorPos + offset);
console.log("Insert text is: ", insertText);
}
function onKeyDown(event) {
sub = false;
if (event.keyCode === 8 || event.keyCode === 46) {
getTextToBeDeleted(event);
} else if (event.keyCode === 90 && event.ctrlKey === true) { //Note: none of this works to fix undo & redo!
sendUndo(event);
} else if (event.keyCode === 89 && event.ctrlKey === true) {
sendRedo(event);
}
}
function getTextToBeDeleted(event) {
// getSelected();
// console.log(selectedText);
// console.log(selectedLen);
event.preventDefault();
var deletedText = "";
var cursorPos = getCursor();
if (selectedText) {
deletedText = selectedText;
console.log("Delete text is: ", deletedText);
sendDeleteOp("delete", deletedText);
} else if (event.keyCode === 8 && cursorPos !== 0) { //on backspace, and not at start of doc
cursorPos = cursorPos-1;
deletedText = textElem.value[cursorPos];
console.log("Delete text is: ", deletedText);
sendDeleteOp("backspace", deletedText);
} else if (event.keyCode === 46 && cursorPos !== textElem.value.length) { //on delete, and not at end of doc
deletedText = textElem.value[cursorPos];
console.log("Delete text is: ", deletedText);
sendDeleteOp("delete", deletedText);
} else {return;}
textElem.value = textElem.value.substring(0, cursorPos) + textElem.value.substring(cursorPos + deletedText.length);
setCursor(cursorPos, cursorPos);
}
function domChanged(event) {
/* var compareDoc = doc.snapshot;
var compareDocRep = compareDoc.replace(/\r/g, "");
var windowsSucks = textElem.value.replace(/\r/g, "");*/
if (sub === true && doc.snapshot !== textElem.value) {
console.error("ERROR: Document mismatch");
console.error("Document: ", escape(doc.snapshot));
console.error("TextElem.value: ", escape(textElem.value));
}
/* for (var i=0; i<compareDoc.length; i++) {
if (compareDoc[i] !== windowsSucks[i]) {
console.error("Character mismatch at position " + i);
console.error(compareDoc[i]);
console.error(windowsSucks[i]);
}
}
}*/
}
/* function onClick(event) {
}
*/
function getSelected(event) {
selectedText = textElem.value.substring(document.activeElement.selectionStart, document.activeElement.selectionEnd);
selectedLen = document.activeElement.selectionEnd - document.activeElement.selectionStart;
}
function doCut(event) {
getSelected();
// console.log("Cut event! Whoo!");
// console.log(selectedText);
deletedText = selectedText;
sendDeleteOp("delete", deletedText);
}
function doPaste(event) {
getSelected();
// console.log("Paste event! Whoo!");
// console.log(event);
}
function doCopy(event) {
// console.log("Copy event! Whoo!");
selectedText = "";
selectedLen = 0;
}
function sendUndo(event) {
event.preventDefault();
console.log("Undo caught");
}
function sendRedo(event) {
event.preventDefault();
console.log("Redo caught");
}
function getCursor() {
return textElem.selectionStart;
}
function setCursor(cursorPos, selecEnd) {
if (textElem.setSelectionRange) {
textElem.focus();
textElem.setSelectionRange(cursorPos, selecEnd);
} else if (textElem.createTextRange) {
var range = textElem.createTextRange();
range.collapse(true);
range.moveEnd('character', cursorPos);
range.moveStart('character', selecEnd);
range.select();
}
// console.log("??" + cursorPos);
}
//functions to send insert and delete ops
function sendInsertOp(insertText, cursorPos) {
var op = [{i: insertText, p: cursorPos}];
doc.submitOp(op, doc.version);
printOp(op);
}
function sendDeleteOp(type, deletedText) {
var op;
var cursorPos = getCursor();
if (type === "backspace") {
op = [{d: deletedText, p: cursorPos - 1}];
} else { //if (type === "delete") {
op = [{d: deletedText, p: cursorPos}];
}
selectedText = "";
selectedLen = 0;
doc.submitOp(op, doc.version);
printOp(op);
}
function sendCombinedOp(insertText, cursorPos) {
var op = [{d: selectedText, p: cursorPos}, {i: insertText, p: cursorPos}];
selectedText = "";
selectedLen = 0;
doc.submitOp(op, doc.version);
printOp(op);
}
function printOp(op) {
// console.log("Applying the op ");
// console.log(op);
// console.log(" to ", escape(textElem.value));
sub = true;
console.log("Doc.snapshot is: ")
console.log(doc.snapshot);
// console.log("Comparedoc: ", escape(compareDoc));
// console.log("Document now reads: \"" + compareDoc + "\""); //commented out while working offline
}
}
setUp();
/*
Not dealt with yet:
- cross browser compatability (urgh)
- getting external ops
- clean up the yuk
- undo/redo in browser
- how the freak do I deal with ppl choosing delete/undo/redo from the right click menu??
*/
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment