|
| 1 | +var Spade = function Spade() { |
| 2 | + this.stack = []; |
| 3 | +} |
| 4 | +Spade.prototype = { |
| 5 | + track: function(_elem) { |
| 6 | + this.target = _elem; |
| 7 | + |
| 8 | + var spade = this; |
| 9 | + |
| 10 | + var el = document.createElement("div"); |
| 11 | + |
| 12 | + keyHook = null; |
| 13 | + if(_elem.textInput && _elem.textInput.getElement) { |
| 14 | + keyHook = _elem.textInput.getElement(); |
| 15 | + } else { |
| 16 | + keyHook = _elem; |
| 17 | + } |
| 18 | + keyHook.addEventListener("keydown", function(_event) {spade.createEvent(spade.target)}); |
| 19 | + //Maybe this is needed depending on Firefox/other browsers? Duplicate non-diff events get compiled down. |
| 20 | + keyHook.addEventListener("keyup", function(_event) {spade.createEvent(spade.target)}); |
| 21 | + _elem.addEventListener("mouseup", function(_event) {spade.createEvent(spade.target)}); |
| 22 | + }, |
| 23 | + createEvent: function(_target) { |
| 24 | + if(_target.getValue) { |
| 25 | + this.stack.push({ |
| 26 | + "startPos":_target.selection.getCursor(), |
| 27 | + "endPos":_target.selection.getSelectionAnchor(), |
| 28 | + "content":_target.getValue(), |
| 29 | + "timestamp":(new Date()).getTime() |
| 30 | + }); |
| 31 | + } else { |
| 32 | + this.stack.push({ |
| 33 | + "startPos":_target.selectionStart, |
| 34 | + "endPos":_target.selectionEnd, |
| 35 | + "content":_target.value, |
| 36 | + "timestamp":(new Date()).getTime() |
| 37 | + }); |
| 38 | + } |
| 39 | + }, |
| 40 | + compile: function() { |
| 41 | + var compiledStack = []; |
| 42 | + if(this.stack.length > 0) { |
| 43 | + var startTime = this.stack[0].timestamp; |
| 44 | + var sum = 0; |
| 45 | + var sum2 = 0; |
| 46 | + for(var i = 0; i < this.stack.length; i++) { |
| 47 | + var c = this.stack[i]; |
| 48 | + var adjustedTimestamp = c.timestamp - startTime; |
| 49 | + |
| 50 | + var tString = ""; //The changed string. |
| 51 | + var fIndex = null; //The first index of changes. |
| 52 | + var eIndex = null; //The last index of changes. |
| 53 | + var dCount = 0; //Amount of character changes. |
| 54 | + if(i >= 1) { |
| 55 | + var p = this.stack[i - 1]; |
| 56 | + var isOkay = false; |
| 57 | + for(var key in p) { |
| 58 | + if(key != "timestamp") { |
| 59 | + if(typeof p[key] === "string") { |
| 60 | + if(p[key] !== c[key]) { |
| 61 | + isOkay = true; |
| 62 | + } |
| 63 | + } else { |
| 64 | + for(var key2 in p[key]) { |
| 65 | + if(c[key][key2] !== undefined) { |
| 66 | + if(p[key][key2] !== c[key][key2]) { |
| 67 | + isOkay = true; |
| 68 | + } |
| 69 | + } else { |
| 70 | + console.warn("Warning: c[key][key2] doesn't exist, but p[key][key2] does."); |
| 71 | + isOkay = true; |
| 72 | + } |
| 73 | + } |
| 74 | + } |
| 75 | + } |
| 76 | + } |
| 77 | + if(!isOkay) { |
| 78 | + sum2++; |
| 79 | + continue; |
| 80 | + } |
| 81 | + sum++; |
| 82 | + if(p.content != c.content) { |
| 83 | + //Check from the start to the end, which characters are different. |
| 84 | + for(var j = 0; j < Math.max(p.content.length, c.content.length); j++) { |
| 85 | + if(p.content.charAt(j) === c.content.charAt(j)) { |
| 86 | + if(fIndex != null) { |
| 87 | + tString += c.content.charAt(j); |
| 88 | + dCount++; |
| 89 | + } |
| 90 | + } else { |
| 91 | + tString += c.content.charAt(j); |
| 92 | + if(fIndex === null) { |
| 93 | + fIndex = j; |
| 94 | + } |
| 95 | + dCount++; |
| 96 | + } |
| 97 | + } |
| 98 | + //Check from the end to the start, which characters are different. |
| 99 | + for(var j = 0; j < Math.min(p.content.length, c.content.length) - fIndex; j++) { |
| 100 | + if(p.content.charAt(p.content.length - 1 - j) !== c.content.charAt(c.content.length - 1 - j)) { |
| 101 | + if(eIndex == null) { |
| 102 | + eIndex = j; |
| 103 | + break; |
| 104 | + } |
| 105 | + } |
| 106 | + } |
| 107 | + //This accounts for the fact when changing from "aa" to "aaa" (for example). |
| 108 | + if(eIndex === null) { |
| 109 | + eIndex = Math.min(p.content.length, c.content.length) - fIndex; |
| 110 | + } |
| 111 | + tString = tString.substring(0, tString.length - eIndex); |
| 112 | + } |
| 113 | + } else { |
| 114 | + tString = c.content; |
| 115 | + fIndex = 0; |
| 116 | + eIndex = tString.length; |
| 117 | + } |
| 118 | + compiledStack.push({ |
| 119 | + "timestamp":adjustedTimestamp, |
| 120 | + "difContent":tString, |
| 121 | + "difFIndex":fIndex, |
| 122 | + "difEIndex":eIndex, |
| 123 | + "selFIndex":c.startPos, |
| 124 | + "selEIndex":c.endPos |
| 125 | + }); |
| 126 | + } |
| 127 | + } else { |
| 128 | + //Just return the empty array. |
| 129 | + } |
| 130 | + return compiledStack; |
| 131 | + }, |
| 132 | + play: function(_stack, _elem) { |
| 133 | + if(_stack.length === 0) { |
| 134 | + console.warn("SPADE: No events to play.") |
| 135 | + return |
| 136 | + } |
| 137 | + if(_elem.setValue) { |
| 138 | + _elem.setValue(_stack[0].difContent); |
| 139 | + } else { |
| 140 | + _elem.value = _stack[0].difContent |
| 141 | + } |
| 142 | + _stack = _stack.slice(); |
| 143 | + _stack.shift(); |
| 144 | + var curTime, dTime; |
| 145 | + var elapsedTime = 0; |
| 146 | + var prevTime = (new Date()).getTime(); |
| 147 | + var playbackInterval = setInterval(function() { |
| 148 | + curTime = (new Date()).getTime(); |
| 149 | + dTime = curTime - prevTime; |
| 150 | + dTime *= 1; //Multiply for faster/slower playback speeds. |
| 151 | + elapsedTime += dTime; |
| 152 | + var tArray = _stack.filter(function(_event) { |
| 153 | + return ((_event.timestamp) >= (elapsedTime - dTime)) && ((_event.timestamp) < (elapsedTime)); |
| 154 | + }); |
| 155 | + for(var i = 0; i < tArray.length; i++) { |
| 156 | + var tEvent = tArray[i]; |
| 157 | + var oVal = null; |
| 158 | + if(_elem.getValue) { |
| 159 | + oVal = _elem.getValue(); |
| 160 | + } else { |
| 161 | + oVal = _elem.value; |
| 162 | + } |
| 163 | + if(tEvent.difFIndex !== null && tEvent.difEIndex !== null) { |
| 164 | + if(_elem.setValue) { |
| 165 | + _elem.setValue(oVal.substring(0, tEvent.difFIndex) + tEvent.difContent + oVal.substring(oVal.length - tEvent.difEIndex, oVal.length)); |
| 166 | + } else { |
| 167 | + _elem.value = oVal.substring(0, tEvent.difFIndex) + tEvent.difContent + oVal.substring(oVal.length - tEvent.difEIndex, oVal.length) |
| 168 | + } |
| 169 | + } |
| 170 | + if(_elem.selection && _elem.selection.moveCursorToPosition) { |
| 171 | + //Maybe this will work someday |
| 172 | + _elem.selection.moveCursorToPosition(tEvent.selFIndex); |
| 173 | + _elem.selection.setSelectionAnchor(tEvent.selEIndex.row, tEvent.selEIndex.column); |
| 174 | + _elem.selection.selectTo(tEvent.selFIndex.row, tEvent.selFIndex.column); |
| 175 | + } else { |
| 176 | + //Likewise |
| 177 | + _elem.focus(); |
| 178 | + _elem.setSelectionRange(tEvent.selFIndex, tEvent.selEIndex); |
| 179 | + } |
| 180 | + } |
| 181 | + if(_stack[_stack.length - 1] === undefined || elapsedTime > _stack[_stack.length - 1].timestamp) { |
| 182 | + clearInterval(playbackInterval); |
| 183 | + } |
| 184 | + prevTime = curTime; |
| 185 | + }, 10); |
| 186 | + }, |
| 187 | + debugPlay: function(_stack) { |
| 188 | + var area = document.createElement('textarea'); |
| 189 | + area.zIndex = 9999; |
| 190 | + area.style.width = "512px"; |
| 191 | + area.style.height = "512px"; |
| 192 | + area.style.position = "absolute"; |
| 193 | + area.style.left = "100px"; |
| 194 | + area.style.top = "100px"; |
| 195 | + document.body.appendChild(area); |
| 196 | + this.play(_stack, area); |
| 197 | + }, |
| 198 | + condense: function(_stack) { |
| 199 | + var compressedArray = []; |
| 200 | + for(var i = 0; i < _stack.length; i++) { |
| 201 | + var u = _stack[i]; |
| 202 | + compressedArray.push([ |
| 203 | + u.timestamp, |
| 204 | + u.difContent, |
| 205 | + u.difFIndex, |
| 206 | + u.difEIndex, |
| 207 | + u.selFIndex.row, |
| 208 | + u.selFIndex.column, |
| 209 | + u.selEIndex.row, |
| 210 | + u.selEIndex.column |
| 211 | + ]); |
| 212 | + } |
| 213 | + return compressedArray; |
| 214 | + }, |
| 215 | + expand: function(_array) { |
| 216 | + var uncompressedArray = []; |
| 217 | + for(var i = 0 ; i < _array.length; i++) { |
| 218 | + var c = _array[i]; |
| 219 | + uncompressedArray.push({ |
| 220 | + "timestamp":c[0], |
| 221 | + "difContent":c[1], |
| 222 | + "difFIndex":c[2], |
| 223 | + "difEIndex":c[3], |
| 224 | + "selFIndex":{ |
| 225 | + "row":c[4], |
| 226 | + "column":c[5] |
| 227 | + }, |
| 228 | + "selEIndex":{ |
| 229 | + "row":c[6], |
| 230 | + "column":c[7] |
| 231 | + }, |
| 232 | + }); |
| 233 | + } |
| 234 | + return uncompressedArray; |
| 235 | + } |
| 236 | +} |
0 commit comments