Created
May 15, 2011 04:49
-
-
Save josephg/972894 to your computer and use it in GitHub Desktop.
Sarah's ot code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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