finished text editing demo
This commit is contained in:
parent
2a10fe45fa
commit
184f0efdff
File diff suppressed because one or more lines are too long
2
build/browser/Frameworks/JsonYatta.min.js
vendored
2
build/browser/Frameworks/JsonYatta.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
build/browser/Frameworks/TextYatta.min.js
vendored
2
build/browser/Frameworks/TextYatta.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
build/browser/Types/BasicTypes.min.js
vendored
2
build/browser/Types/BasicTypes.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
build/browser/Types/JsonTypes.min.js
vendored
2
build/browser/Types/JsonTypes.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
build/browser/Types/StructuredTypes.min.js
vendored
2
build/browser/Types/StructuredTypes.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
build/browser/Types/TextTypes.min.js
vendored
2
build/browser/Types/TextTypes.min.js
vendored
File diff suppressed because one or more lines are too long
@ -3,4 +3,4 @@
|
||||
|
||||
|
||||
},{}]},{},[1])
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL2Rtb25hZC9Ecm9wYm94L1lhdHRhIS9ub2RlX21vZHVsZXMvZ3VscC1icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCIvaG9tZS9kbW9uYWQvRHJvcGJveC9ZYXR0YSEvbGliL1R5cGVzL1htbFR5cGVzLmNvZmZlZSJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0FBO0FBQ0E7QUFDQSIsImZpbGUiOiJnZW5lcmF0ZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlc0NvbnRlbnQiOlsiKGZ1bmN0aW9uIGUodCxuLHIpe2Z1bmN0aW9uIHMobyx1KXtpZighbltvXSl7aWYoIXRbb10pe3ZhciBhPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7aWYoIXUmJmEpcmV0dXJuIGEobywhMCk7aWYoaSlyZXR1cm4gaShvLCEwKTt0aHJvdyBuZXcgRXJyb3IoXCJDYW5ub3QgZmluZCBtb2R1bGUgJ1wiK28rXCInXCIpfXZhciBmPW5bb109e2V4cG9ydHM6e319O3Rbb11bMF0uY2FsbChmLmV4cG9ydHMsZnVuY3Rpb24oZSl7dmFyIG49dFtvXVsxXVtlXTtyZXR1cm4gcyhuP246ZSl9LGYsZi5leHBvcnRzLGUsdCxuLHIpfXJldHVybiBuW29dLmV4cG9ydHN9dmFyIGk9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtmb3IodmFyIG89MDtvPHIubGVuZ3RoO28rKylzKHJbb10pO3JldHVybiBzfSkiLCJcblxuLy8jIHNvdXJjZU1hcHBpbmdVUkw9ZGF0YTphcHBsaWNhdGlvbi9qc29uO2Jhc2U2NCxleUoyWlhKemFXOXVJam96TENKbWFXeGxJam9pTDJodmJXVXZaRzF2Ym1Ga0wwUnliM0JpYjNndldXRjBkR0VoTDJ4cFlpOVVlWEJsY3k5WWJXeFVlWEJsY3k1amIyWm1aV1VpTENKemIzVnlZMlZTYjI5MElqb2lJaXdpYzI5MWNtTmxjeUk2V3lJdmFHOXRaUzlrYlc5dVlXUXZSSEp2Y0dKdmVDOVpZWFIwWVNFdmJHbGlMMVI1Y0dWekwxaHRiRlI1Y0dWekxtTnZabVpsWlNKZExDSnVZVzFsY3lJNlcxMHNJbTFoY0hCcGJtZHpJam9pUVVGNVkwY2lMQ0p6YjNWeVkyVnpRMjl1ZEdWdWRDSTZXeUlpWFgwPSJdfQ==
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL2Rtb25hZC9Ecm9wYm94L1lhdHRhIS9ub2RlX21vZHVsZXMvZ3VscC1icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCIvaG9tZS9kbW9uYWQvRHJvcGJveC9ZYXR0YSEvbGliL1R5cGVzL1htbFR5cGVzLmNvZmZlZSJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0FBO0FBQ0E7QUFDQSIsImZpbGUiOiJnZW5lcmF0ZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlc0NvbnRlbnQiOlsiKGZ1bmN0aW9uIGUodCxuLHIpe2Z1bmN0aW9uIHMobyx1KXtpZighbltvXSl7aWYoIXRbb10pe3ZhciBhPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7aWYoIXUmJmEpcmV0dXJuIGEobywhMCk7aWYoaSlyZXR1cm4gaShvLCEwKTt0aHJvdyBuZXcgRXJyb3IoXCJDYW5ub3QgZmluZCBtb2R1bGUgJ1wiK28rXCInXCIpfXZhciBmPW5bb109e2V4cG9ydHM6e319O3Rbb11bMF0uY2FsbChmLmV4cG9ydHMsZnVuY3Rpb24oZSl7dmFyIG49dFtvXVsxXVtlXTtyZXR1cm4gcyhuP246ZSl9LGYsZi5leHBvcnRzLGUsdCxuLHIpfXJldHVybiBuW29dLmV4cG9ydHN9dmFyIGk9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtmb3IodmFyIG89MDtvPHIubGVuZ3RoO28rKylzKHJbb10pO3JldHVybiBzfSkiLCJcblxuLy8jIHNvdXJjZU1hcHBpbmdVUkw9ZGF0YTphcHBsaWNhdGlvbi9qc29uO2Jhc2U2NCxleUoyWlhKemFXOXVJam96TENKbWFXeGxJam9pTDJodmJXVXZaRzF2Ym1Ga0wwUnliM0JpYjNndldXRjBkR0VoTDJ4cFlpOVVlWEJsY3k5WWJXeFVlWEJsY3k1amIyWm1aV1VpTENKemIzVnlZMlZTYjI5MElqb2lJaXdpYzI5MWNtTmxjeUk2V3lJdmFHOXRaUzlrYlc5dVlXUXZSSEp2Y0dKdmVDOVpZWFIwWVNFdmJHbGlMMVI1Y0dWekwxaHRiRlI1Y0dWekxtTnZabVpsWlNKZExDSnVZVzFsY3lJNlcxMHNJbTFoY0hCcGJtZHpJam9pUVVFd1kwY2lMQ0p6YjNWeVkyVnpRMjl1ZEdWdWRDSTZXeUlpWFgwPSJdfQ==
|
||||
|
File diff suppressed because one or more lines are too long
4
build/browser/index.min.js
vendored
4
build/browser/index.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,2 +1,2 @@
|
||||
(function(){var e,t,n,o;o=require("../Types/TextTypes"),t=require("../HistoryBuffer"),e=require("../Engine"),n=function(){function n(n,r){var i,u;this.HB=new t(n),u=o(this.HB),this.engine=new e(this.HB,u.parser),this.connector=new r(this.engine,this.HB,u.execution_listener),i=new u.types.Word(void 0),this.HB.addOperation(i).execute(),this.root_element=i}return n.prototype.getRootElement=function(){return this.root_element},n.prototype.getEngine=function(){return this.engine},n.prototype.getConnector=function(){return this.connector},n.prototype.getHistoryBuffer=function(){return this.HB},n.prototype.getUserId=function(){return this.HB.getUserId()},n.prototype.val=function(){return this.root_element.val()},n.prototype.insertText=function(e,t){return this.root_element.insertText(e,t)},n.prototype.deleteText=function(e,t){return this.root_element.deleteText(e,t)},n.prototype.replaceText=function(e){return this.root_element.replaceText(e)},n}(),module.exports=n,"undefined"!=typeof window&&null!==window&&(null==window.Y&&(window.Y={}),window.Y.TextYatta=n)}).call(this);
|
||||
(function(){var e,t,n,o;o=require("../Types/TextTypes"),t=require("../HistoryBuffer"),e=require("../Engine"),n=function(){function n(n,r){var i,u;this.HB=new t(n),u=o(this.HB),this.engine=new e(this.HB,u.parser),this.connector=new r(this.engine,this.HB,u.execution_listener,this),i=new u.types.Word({creator:"_",op_number:"_"}),this.HB.addOperation(i).execute(),this.root_element=i}return n.prototype.getRootElement=function(){return this.root_element},n.prototype.getEngine=function(){return this.engine},n.prototype.getConnector=function(){return this.connector},n.prototype.getHistoryBuffer=function(){return this.HB},n.prototype.getUserId=function(){return this.HB.getUserId()},n.prototype.val=function(){return this.root_element.val()},n.prototype.insertText=function(e,t){return this.root_element.insertText(e,t)},n.prototype.deleteText=function(e,t){return this.root_element.deleteText(e,t)},n.prototype.bind=function(e){return this.root_element.bind(e)},n.prototype.replaceText=function(e){return this.root_element.replaceText(e)},n}(),module.exports=n,"undefined"!=typeof window&&null!==window&&(null==window.Y&&(window.Y={}),window.Y.TextYatta=n)}).call(this);
|
||||
//# sourceMappingURL=../Frameworks/TextYatta.js.map
|
@ -1 +1 @@
|
||||
{"version":3,"file":"Frameworks/TextYatta.js","sources":["Frameworks/TextYatta.coffee"],"names":[],"mappings":"CACA,WAAA,GAAA,GAAA,EAAA,EAAA,CAAA,GAA2B,QAAQ,sBACnC,EAAgB,QAAQ,oBACxB,EAAS,QAAQ,aAKX,EAAA,WAMS,QAAA,GAAC,EAAS,GACrB,GAAA,GAAA,CAAA,MAAC,GAAS,GAAA,GAAc,GACxB,EAAa,EAAyB,KAAC,IACvC,KAAC,OAAa,GAAA,GAAO,KAAC,GAAI,EAAW,QACrC,KAAC,UAAgB,GAAA,GAAU,KAAC,OAAQ,KAAC,GAAI,EAAW,oBAEpD,EAAiB,GAAA,GAAW,MAAM,KAAK,QACvC,KAAC,GAAG,aAAa,GAAY,UAC7B,KAAC,aAAe,QARlB,GAAA,UAaA,eAAgB,iBACd,MAAC,cAdH,EAAA,UAmBA,UAAW,iBACT,MAAC,QApBH,EAAA,UAyBA,aAAc,iBACZ,MAAC,WA1BH,EAAA,UA+BA,iBAAkB,iBAChB,MAAC,IAhCH,EAAA,UAuCA,UAAW,iBACT,MAAC,GAAG,aAxCN,EAAA,UA6CA,IAAK,iBACH,MAAC,aAAa,OA9ChB,EAAA,UAmDA,WAAY,SAAC,EAAK,SAChB,MAAC,aAAa,WAAW,EAAK,IApDhC,EAAA,UAyDA,WAAY,SAAC,EAAK,SAChB,MAAC,aAAa,WAAW,EAAK,IA1DhC,EAAA,UA+DA,YAAa,SAAC,SACZ,MAAC,aAAa,YAAY,SAG9B,OAAO,QAAU,EACd,mBAAA,SAAA,OAAA,SACM,MAAA,OAAA,IACL,OAAO,MACT,OAAO,EAAE,UAAY","sourcesContent":["\ntext_types_uninitialized = require \"../Types/TextTypes\"\nHistoryBuffer = require \"../HistoryBuffer\"\nEngine = require \"../Engine\"\n\n#\n# Framework for Text Datastructures.\n#\nclass TextYatta\n\n #\n # @param {String} user_id Uniqe user id that defines this peer.\n # @param {Connector} Connector The connector defines how you connect to the other peers.\n #\n constructor: (user_id, Connector)->\n @HB = new HistoryBuffer user_id\n text_types = text_types_uninitialized @HB\n @engine = new Engine @HB, text_types.parser\n @connector = new Connector @engine, @HB, text_types.execution_listener\n\n first_word = new text_types.types.Word undefined\n @HB.addOperation(first_word).execute()\n @root_element = first_word\n\n #\n # @result Word\n #\n getRootElement: ()->\n @root_element\n\n #\n # @see Engine\n #\n getEngine: ()->\n @engine\n\n #\n # Get the initialized connector.\n #\n getConnector: ()->\n @connector\n\n #\n # @see HistoryBuffer\n #\n getHistoryBuffer: ()->\n @HB\n\n #\n # Get the UserId from the HistoryBuffer object.\n # In most cases this will be the same as the user_id value with which\n # JsonYatta was initialized (Depending on the HistoryBuffer implementation).\n #\n getUserId: ()->\n @HB.getUserId()\n\n #\n # @see JsonType.val\n #\n val: ()->\n @root_element.val()\n\n #\n # @see Word.insertText\n #\n insertText: (pos, content)->\n @root_element.insertText pos, content\n\n #\n # @see Word.deleteText\n #\n deleteText: (pos, length)->\n @root_element.deleteText pos, length\n\n #\n # @see Word.replaceText\n #\n replaceText: (text)->\n @root_element.replaceText text\n\n\nmodule.exports = TextYatta\nif window?\n if not window.Y?\n window.Y = {}\n window.Y.TextYatta = TextYatta\n"],"sourceRoot":"/source/"}
|
||||
{"version":3,"file":"Frameworks/TextYatta.js","sources":["Frameworks/TextYatta.coffee"],"names":[],"mappings":"CACA,WAAA,GAAA,GAAA,EAAA,EAAA,CAAA,GAA2B,QAAQ,sBACnC,EAAgB,QAAQ,oBACxB,EAAS,QAAQ,aAKX,EAAA,WAMS,QAAA,GAAC,EAAS,GACrB,GAAA,GAAA,CAAA,MAAC,GAAS,GAAA,GAAc,GACxB,EAAa,EAAyB,KAAC,IACvC,KAAC,OAAa,GAAA,GAAO,KAAC,GAAI,EAAW,QACrC,KAAC,UAAgB,GAAA,GAAU,KAAC,OAAQ,KAAC,GAAI,EAAW,mBAAoB,MAExE,EAAiB,GAAA,GAAW,MAAM,MAAM,QAAS,IAAK,UAAW,MACjE,KAAC,GAAG,aAAa,GAAY,UAC7B,KAAC,aAAe,QARlB,GAAA,UAaA,eAAgB,iBACd,MAAC,cAdH,EAAA,UAmBA,UAAW,iBACT,MAAC,QApBH,EAAA,UAyBA,aAAc,iBACZ,MAAC,WA1BH,EAAA,UA+BA,iBAAkB,iBAChB,MAAC,IAhCH,EAAA,UAuCA,UAAW,iBACT,MAAC,GAAG,aAxCN,EAAA,UA6CA,IAAK,iBACH,MAAC,aAAa,OA9ChB,EAAA,UAmDA,WAAY,SAAC,EAAK,SAChB,MAAC,aAAa,WAAW,EAAK,IApDhC,EAAA,UAyDA,WAAY,SAAC,EAAK,SAChB,MAAC,aAAa,WAAW,EAAK,IA1DhC,EAAA,UA+DA,KAAM,SAAC,SACL,MAAC,aAAa,KAAK,IAhErB,EAAA,UAqEA,YAAa,SAAC,SACZ,MAAC,aAAa,YAAY,SAG9B,OAAO,QAAU,EACd,mBAAA,SAAA,OAAA,SACM,MAAA,OAAA,IACL,OAAO,MACT,OAAO,EAAE,UAAY","sourcesContent":["\ntext_types_uninitialized = require \"../Types/TextTypes\"\nHistoryBuffer = require \"../HistoryBuffer\"\nEngine = require \"../Engine\"\n\n#\n# Framework for Text Datastructures.\n#\nclass TextYatta\n\n #\n # @param {String} user_id Uniqe user id that defines this peer.\n # @param {Connector} Connector The connector defines how you connect to the other peers.\n #\n constructor: (user_id, Connector)->\n @HB = new HistoryBuffer user_id\n text_types = text_types_uninitialized @HB\n @engine = new Engine @HB, text_types.parser\n @connector = new Connector @engine, @HB, text_types.execution_listener, @\n\n first_word = new text_types.types.Word {creator: '_', op_number: '_'}\n @HB.addOperation(first_word).execute()\n @root_element = first_word\n\n #\n # @result Word\n #\n getRootElement: ()->\n @root_element\n\n #\n # @see Engine\n #\n getEngine: ()->\n @engine\n\n #\n # Get the initialized connector.\n #\n getConnector: ()->\n @connector\n\n #\n # @see HistoryBuffer\n #\n getHistoryBuffer: ()->\n @HB\n\n #\n # Get the UserId from the HistoryBuffer object.\n # In most cases this will be the same as the user_id value with which\n # JsonYatta was initialized (Depending on the HistoryBuffer implementation).\n #\n getUserId: ()->\n @HB.getUserId()\n\n #\n # @see JsonType.val\n #\n val: ()->\n @root_element.val()\n\n #\n # @see Word.insertText\n #\n insertText: (pos, content)->\n @root_element.insertText pos, content\n\n #\n # @see Word.deleteText\n #\n deleteText: (pos, length)->\n @root_element.deleteText pos, length\n\n #\n # @see Word.bind\n #\n bind: (textarea)->\n @root_element.bind textarea\n\n #\n # @see Word.replaceText\n #\n replaceText: (text)->\n @root_element.replaceText text\n\n\nmodule.exports = TextYatta\nif window?\n if not window.Y?\n window.Y = {}\n window.Y.TextYatta = TextYatta\n"],"sourceRoot":"/source/"}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,2 +1,2 @@
|
||||
(function(){var t,e={}.hasOwnProperty,n=function(t,n){function r(){this.constructor=t}for(var i in n)e.call(n,i)&&(t[i]=n[i]);return r.prototype=n.prototype,t.prototype=new r,t.__super__=n.prototype,t};t=require("./StructuredTypes"),module.exports=function(e){var r,i,o,l,u,s;return u=t(e),s=u.types,l=u.parser,r=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return n(e,t),e}(s.Delete),l.TextDelete=l.Delete,i=function(t){function e(t,n,r,i,o){if(this.content=t,null==r||null==i)throw new Error("You must define prev, and next for TextInsert-types!");e.__super__.constructor.call(this,n,r,i,o)}return n(e,t),e.prototype.getLength=function(){return this.isDeleted()?0:this.content.length},e.prototype.val=function(){return this.isDeleted()?"":this.content},e.prototype._encode=function(){var t;return t={type:"TextInsert",content:this.content,uid:this.getUid(),prev:this.prev_cl.getUid(),next:this.next_cl.getUid()},null!=this.origin&&this.origin!==this.prev_cl&&(t.origin=this.origin.getUid()),t},e}(s.Insert),l.TextInsert=function(t){var e,n,r,o,l;return e=t.content,l=t.uid,o=t.prev,n=t.next,r=t.origin,new i(e,l,o,n,r)},o=function(t){function o(t,e,n,r,i,l){o.__super__.constructor.call(this,t,e,n,r,i,l)}return n(o,t),o.prototype.insertText=function(t,n){var r,o,l,u,s,a;for(o=this.getOperationByPosition(t),a=[],u=0,s=n.length;s>u;u++)r=n[u],l=new i(r,void 0,o.prev_cl,o),a.push(e.addOperation(l).execute());return a},o.prototype.deleteText=function(t,n){var i,o,l,u,a,c;for(u=this.getOperationByPosition(t),o=[],c=[],l=a=0;n>=0?n>a:a>n;l=n>=0?++a:--a){for(i=e.addOperation(new r(void 0,u)).execute(),u=u.next_cl;u.isDeleted()&&!(u instanceof s.Delimiter);){if(u instanceof s.Delimiter)throw new Error("You can't delete more than there is..");u=u.next_cl}if(o.push(i._encode()),u instanceof s.Delimiter)break;c.push(void 0)}return c},o.prototype.replaceText=function(t){var n;if(null!=this.replace_manager)return n=e.addOperation(new o(void 0)).execute(),n.insertText(0,t),this.replace_manager.replace(n);throw new Error("This type is currently not maintained by a ReplaceManager!")},o.prototype.val=function(){var t,e;return t=function(){var t,n,r,i;for(r=this.toArray(),i=[],t=0,n=r.length;n>t;t++)e=r[t],i.push(null!=e.val?e.val():"");return i}.call(this),t.join("")},o.prototype.setReplaceManager=function(t){return this.saveOperation("replace_manager",t),this.validateSavedOperations},o.prototype.bind=function(t){var n,r,i,o;return o=this,t.value=this.val(),r=null,n=null,this.on("insert",function(i,l){var u,s,a,c;return l.creator!==e.getUserId()?(a=l.getPosition(),u=function(t){return a>=t?t:t+=1},s=u(t.selectionStart),c=u(t.selectionEnd),null!=r&&(n+=1,r=u(r)),t.value=o.val(),t.setSelectionRange(s,c)):void 0}),this.on("delete",function(i,l){var u,s,a,c;return l.creator!==e.getUserId()?(a=l.getPosition(),u=function(t){return a>=t?t:t-=1},s=u(t.selectionStart),c=u(t.selectionEnd),null!=r&&(n-=1,r=u(r)),t.value=o.val(),t.setSelectionRange(s,c)):void 0}),i=function(){var e,i,l,u;return null!=r?(l=t.value.length-n,e=Math.min(t.selectionStart,t.selectionEnd),0>l?(i=Math.min(e,r),o.deleteText(i,Math.abs(l))):l>0&&(u=t.value.substring(r,r+l),o.insertText(r,u)),r=null,n=null):void 0},t.onkeydown=function(){var e;return null!=r&&i(),e=Math.abs(t.selectionEnd-t.selectionStart),r=Math.min(t.selectionStart,t.selectionEnd),o.deleteText(r,e),n=t.value.length-e},t.onkeyup=function(){return i()}},o.prototype._encode=function(){var t;return t={type:"Word",uid:this.getUid(),beginning:this.beginning.getUid(),end:this.end.getUid()},null!=this.prev_cl&&(t.prev=this.prev_cl.getUid()),null!=this.next_cl&&(t.next=this.next_cl.getUid()),null!=this.origin&&this.origin!==this.prev_cl&&(t.origin=this.origin.getUid()),t},o}(s.ListManager),l.Word=function(t){var e,n,r,i,l,u;return u=t.uid,e=t.beginning,n=t.end,l=t.prev,r=t.next,i=t.origin,new o(u,e,n,l,r,i)},s.TextInsert=i,s.TextDelete=r,s.Word=o,u}}).call(this);
|
||||
(function(){var e,t={}.hasOwnProperty,n=function(e,n){function r(){this.constructor=e}for(var i in n)t.call(n,i)&&(e[i]=n[i]);return r.prototype=n.prototype,e.prototype=new r,e.__super__=n.prototype,e};e=require("./StructuredTypes"),module.exports=function(t){var r,i,o,l,s,a;return s=e(t),a=s.types,l=s.parser,r=function(e){function t(){return t.__super__.constructor.apply(this,arguments)}return n(t,e),t}(a.Delete),l.TextDelete=l.Delete,i=function(e){function t(e,n,r,i,o){if(this.content=e,null==r||null==i)throw new Error("You must define prev, and next for TextInsert-types!");t.__super__.constructor.call(this,n,r,i,o)}return n(t,e),t.prototype.getLength=function(){return this.isDeleted()?0:this.content.length},t.prototype.val=function(){return this.isDeleted()?"":this.content},t.prototype._encode=function(){var e;return e={type:"TextInsert",content:this.content,uid:this.getUid(),prev:this.prev_cl.getUid(),next:this.next_cl.getUid()},null!=this.origin&&this.origin!==this.prev_cl&&(e.origin=this.origin.getUid()),e},t}(a.Insert),l.TextInsert=function(e){var t,n,r,o,l;return t=e.content,l=e.uid,o=e.prev,n=e.next,r=e.origin,new i(t,l,o,n,r)},o=function(e){function o(e,t,n,r,i,l){o.__super__.constructor.call(this,e,t,n,r,i,l)}return n(o,e),o.prototype.insertText=function(e,n){var r,o,l,s,a,u;for(o=this.getOperationByPosition(e),u=[],s=0,a=n.length;a>s;s++)r=n[s],l=new i(r,void 0,o.prev_cl,o),u.push(t.addOperation(l).execute());return u},o.prototype.deleteText=function(e,n){var i,o,l,s,u,c;for(s=this.getOperationByPosition(e),o=[],c=[],l=u=0;n>=0?n>u:u>n;l=n>=0?++u:--u){for(i=t.addOperation(new r(void 0,s)).execute(),s=s.next_cl;s.isDeleted()&&!(s instanceof a.Delimiter);){if(s instanceof a.Delimiter)throw new Error("You can't delete more than there is..");s=s.next_cl}if(o.push(i._encode()),s instanceof a.Delimiter)break;c.push(void 0)}return c},o.prototype.replaceText=function(e){var n;if(null!=this.replace_manager)return n=t.addOperation(new o(void 0)).execute(),n.insertText(0,e),this.replace_manager.replace(n);throw new Error("This type is currently not maintained by a ReplaceManager!")},o.prototype.val=function(){var e,t;return e=function(){var e,n,r,i;for(r=this.toArray(),i=[],e=0,n=r.length;n>e;e++)t=r[e],i.push(null!=t.val?t.val():"");return i}.call(this),e.join("")},o.prototype.setReplaceManager=function(e){return this.saveOperation("replace_manager",e),this.validateSavedOperations},o.prototype.bind=function(e){var n;return n=this,e.value=this.val(),this.on("insert",function(r,i){var o,l,s,a;return i.creator!==t.getUserId()?(s=i.getPosition(),o=function(e){return s>=e?e:e+=1},l=o(e.selectionStart),a=o(e.selectionEnd),e.value=n.val(),e.setSelectionRange(l,a)):void 0}),this.on("delete",function(t,r){var i,o,l,s;return l=r.getPosition(),i=function(e){return l>e?e:e-=1},o=i(e.selectionStart),s=i(e.selectionEnd),e.value=n.val(),e.setSelectionRange(o,s)}),e.onkeypress=function(t){var r,i,o;return r=String.fromCharCode(t.keyCode),r.length>0?(console.log(r.length),o=Math.min(e.selectionStart,e.selectionEnd),i=Math.abs(e.selectionEnd-e.selectionStart),n.deleteText(o,i),n.insertText(o,r)):t.preventDefault()},e.onkeydown=function(t){var r,i,o,l,s;if(l=Math.min(e.selectionStart,e.selectionEnd),i=Math.abs(e.selectionEnd-e.selectionStart),null!=t.keyCode&&8===t.keyCode){if(i>0)n.deleteText(l,i);else if(null!=t.ctrlKey&&t.ctrlKey){for(s=e.value,o=l,r=0,l>0&&(o--,r++);o>0&&" "!==s[o]&&"\n"!==s[o];)o--,r++;n.deleteText(o,l-o),e.setSelectionRange(o,o)}else n.deleteText(l-1,1);return t.preventDefault()}return null!=t.keyCode&&46===t.keyCode?(i>0?n.deleteText(l,i):n.deleteText(l,1),t.preventDefault()):void 0}},o.prototype._encode=function(){var e;return e={type:"Word",uid:this.getUid(),beginning:this.beginning.getUid(),end:this.end.getUid()},null!=this.prev_cl&&(e.prev=this.prev_cl.getUid()),null!=this.next_cl&&(e.next=this.next_cl.getUid()),null!=this.origin&&this.origin!==this.prev_cl&&(e.origin=this.origin.getUid()),e},o}(a.ListManager),l.Word=function(e){var t,n,r,i,l,s;return s=e.uid,t=e.beginning,n=e.end,l=e.prev,r=e.next,i=e.origin,new o(s,t,n,l,r,i)},a.TextInsert=i,a.TextDelete=r,a.Word=o,s}}).call(this);
|
||||
//# sourceMappingURL=../Types/TextTypes.js.map
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
{"version":3,"file":"Types/XmlTypes.js","sources":["Types/XmlTypes.coffee"],"names":[],"mappings":"CA0H8B","sourcesContent":[""],"sourceRoot":"/source/"}
|
||||
{"version":3,"file":"Types/XmlTypes.js","sources":["Types/XmlTypes.coffee"],"names":[],"mappings":"CAwFuB","sourcesContent":[""],"sourceRoot":"/source/"}
|
File diff suppressed because one or more lines are too long
@ -14,7 +14,7 @@ A working widget implementation is [IwcJson.xml](./IwcJson.xml) and the js-file
|
||||
|
||||
```js
|
||||
function init(){
|
||||
Y.createIwcConnector(function(Connector, user_id){
|
||||
Y.createPeerJsConnector(function(Connector, user_id){
|
||||
```
|
||||
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
A working widget implementation is [IwcJson.xml](./IwcJson.xml) and the js-file is [index.js](./index.js)
|
||||
*/
|
||||
function init(){
|
||||
Y.createIwcConnector(function(Connector, user_id){
|
||||
Y.createPeerJsConnector(function(Connector, user_id){
|
||||
/**
|
||||
yatta is the shared json object. If you change something on this object,
|
||||
it will be instantly shared with all the other collaborators.
|
||||
|
@ -1,19 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset=utf-8 />
|
||||
<title>PeerJs Json Example</title>
|
||||
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
|
||||
<script src="../../build/browser/Frameworks/JsonYatta.js"></script>
|
||||
<script src="../../build/browser/Connectors/PeerJsConnector.js"></script>
|
||||
<script src="./index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1> Yatta PeerJs Demo </h1>
|
||||
<p> Open this link in other browsers: <a id="peer_link" target="_blank">Drop me </a> </p>
|
||||
|
||||
<textarea style="width:80%;" rows=40 id="textfield"></textarea>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,54 +0,0 @@
|
||||
## IWC + JSON Example
|
||||
Here, I will give a short overview on how to use the IwcJson Framework in Role-SDK widgets.
|
||||
First you have to include the following libraries in your widget file:
|
||||
```
|
||||
<script src="http://open-app.googlecode.com/files/openapp.js"></script>
|
||||
<script src="http://dbis.rwth-aachen.de/gadgets/iwc/lib/iwc.js"></script>
|
||||
<script src="http://dbis.rwth-aachen.de/~jahns/role-widgets/widgetbundles/libraries/DUIClient.js"></script>
|
||||
<script src="../../build/browser/Frameworks/JsonYatta.min.js"></script>
|
||||
<script src="../../build/browser/Connectors/PeerJsConnector.min.js"></script>
|
||||
<script src="./index.js"></script>
|
||||
```
|
||||
A working widget implementation is [IwcJson.xml](./IwcJson.xml) and the js-file is [index.js](./index.js)
|
||||
|
||||
|
||||
```js
|
||||
var yatta;
|
||||
|
||||
function init(){
|
||||
Y.createPeerJsConnector(function(Connector, user_id){
|
||||
```
|
||||
|
||||
|
||||
yatta is the shared json object. If you change something on this object,
|
||||
it will be instantly shared with all the other collaborators.
|
||||
|
||||
|
||||
```js
|
||||
yatta = new Y.JsonYatta(user_id, Connector);
|
||||
|
||||
var url = window.location.href;
|
||||
var peer_id = location.search
|
||||
var url = url.substring(0,-peer_id.length);
|
||||
peer_id = peer_id.substring(1);
|
||||
document.getElementById("peer_link").setAttribute("href",url+"?"+user_id);
|
||||
|
||||
var textbox = document.getElementById("textfield");
|
||||
if (peer_id.length > 0) {
|
||||
yatta.connector.connectToPeer(peer_id);
|
||||
function f() {
|
||||
if (yatta.val('x') == null) {
|
||||
setTimeout(f, 500);
|
||||
} else {
|
||||
yatta.val('x').bind(textbox);
|
||||
}
|
||||
}
|
||||
f();
|
||||
} else {
|
||||
yatta.val('x', "");
|
||||
yatta.val('x').bind(textbox);
|
||||
}
|
||||
});
|
||||
}
|
||||
window.onload = init
|
||||
```
|
@ -1,49 +0,0 @@
|
||||
|
||||
/**
|
||||
## IWC + JSON Example
|
||||
Here, I will give a short overview on how to use the IwcJson Framework in Role-SDK widgets.
|
||||
First you have to include the following libraries in your widget file:
|
||||
```
|
||||
<script src="http://open-app.googlecode.com/files/openapp.js"></script>
|
||||
<script src="http://dbis.rwth-aachen.de/gadgets/iwc/lib/iwc.js"></script>
|
||||
<script src="http://dbis.rwth-aachen.de/~jahns/role-widgets/widgetbundles/libraries/DUIClient.js"></script>
|
||||
<script src="../../build/browser/Frameworks/JsonYatta.min.js"></script>
|
||||
<script src="../../build/browser/Connectors/PeerJsConnector.min.js"></script>
|
||||
<script src="./index.js"></script>
|
||||
```
|
||||
A working widget implementation is [IwcJson.xml](./IwcJson.xml) and the js-file is [index.js](./index.js)
|
||||
*/
|
||||
var yatta;
|
||||
|
||||
function init(){
|
||||
Y.createPeerJsConnector(function(Connector, user_id){
|
||||
/**
|
||||
yatta is the shared json object. If you change something on this object,
|
||||
it will be instantly shared with all the other collaborators.
|
||||
*/
|
||||
yatta = new Y.JsonYatta(user_id, Connector);
|
||||
|
||||
var url = window.location.href;
|
||||
var peer_id = location.search
|
||||
var url = url.substring(0,-peer_id.length);
|
||||
peer_id = peer_id.substring(1);
|
||||
document.getElementById("peer_link").setAttribute("href",url+"?"+user_id);
|
||||
|
||||
var textbox = document.getElementById("textfield");
|
||||
if (peer_id.length > 0) {
|
||||
yatta.connector.connectToPeer(peer_id);
|
||||
function f() {
|
||||
if (yatta.val('x') == null) {
|
||||
setTimeout(f, 500);
|
||||
} else {
|
||||
yatta.val('x').bind(textbox);
|
||||
}
|
||||
}
|
||||
f();
|
||||
} else {
|
||||
yatta.val('x', "");
|
||||
yatta.val('x').bind(textbox);
|
||||
}
|
||||
});
|
||||
}
|
||||
window.onload = init
|
@ -2,6 +2,6 @@
|
||||
|
||||
Here you find some (hopefully) usefull examples on how to use Yatta!
|
||||
|
||||
|
||||
* [Text Editing](./TextEditing/) Simple collaborative text editing demo with PeerJs and Text Framework
|
||||
* [IWC + Json](./IwcJson/) Tutorial on how to use Yatta! with Json and IWC
|
||||
* [IWC](./IwcJson/) More IWC example widgets.
|
||||
|
59
examples/TextEditing/README.md
Normal file
59
examples/TextEditing/README.md
Normal file
@ -0,0 +1,59 @@
|
||||
## Text Editing Example
|
||||
Here, I will give a short overview on how to enable collaborative text editing with the PeerJs Connector and the TextYatta Framework.
|
||||
First you have to include the following libraries in your html file:
|
||||
```
|
||||
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
|
||||
<script src="../../build/browser/Frameworks/TextYatta.js"></script>
|
||||
<script src="../../build/browser/Connectors/PeerJsConnector.js"></script>
|
||||
<script src="./index.js"></script>
|
||||
```
|
||||
Open [index.html](./index.html) in order to start collaboration.
|
||||
|
||||
|
||||
```js
|
||||
var yatta;
|
||||
|
||||
function init(){
|
||||
Y.createPeerJsConnector(function(Connector, user_id){
|
||||
```
|
||||
|
||||
|
||||
TextYatta is a shared text object. If you change something on this object,
|
||||
it will be instantaneously shared with all the other collaborators.
|
||||
|
||||
|
||||
```js
|
||||
yatta = new Y.TextYatta(user_id, Connector);
|
||||
|
||||
/*
|
||||
Get the url of this frame. If it has a url-encoded parameter
|
||||
we will connect to the foreign peer.
|
||||
*/
|
||||
var url = window.location.href;
|
||||
var peer_id = location.search
|
||||
var url = url.substring(0,-peer_id.length);
|
||||
peer_id = peer_id.substring(1);
|
||||
|
||||
/*
|
||||
Set the shareable link.
|
||||
*/
|
||||
document.getElementById("peer_link").setAttribute("href",url+"?"+user_id);
|
||||
|
||||
/*
|
||||
Connect to other peer.
|
||||
*/
|
||||
if (peer_id.length > 0){
|
||||
yatta.connector.connectToPeer(peer_id);
|
||||
}
|
||||
|
||||
/*
|
||||
Bind yatta to the textfield.
|
||||
|
||||
The .bind property is a method of the Word class. You can also use it with all the other Frameworks in Yatta (e.g. Json).
|
||||
*/
|
||||
var textbox = document.getElementById("textfield");
|
||||
yatta.bind(textbox);
|
||||
});
|
||||
}
|
||||
window.onload = init
|
||||
```
|
21
examples/TextEditing/index.html
Normal file
21
examples/TextEditing/index.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset=utf-8 />
|
||||
<title>PeerJs Json Example</title>
|
||||
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
|
||||
<script src="../../build/browser/Frameworks/TextYatta.js"></script>
|
||||
<script src="../../build/browser/Connectors/PeerJsConnector.js"></script>
|
||||
<script src="./index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1> Text Editing Demo</h1>
|
||||
<p> This demo enables P2P text editing. Open this link in other browsers: <a id="peer_link" target="_blank">Drop me </a> </p>
|
||||
|
||||
<textarea style="width:80%;" rows=40 id="textfield"></textarea>
|
||||
|
||||
<p> <a href="https://github.com/DadaMonad/Yatta/">Yatta</a> is a Framework for Real-Time collaboration on arbitrary data structures.
|
||||
You can find the code for this example <a href="https://github.com/DadaMonad/Yatta/tree/master/examples/TextEditing">here</a>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
54
examples/TextEditing/index.js
Normal file
54
examples/TextEditing/index.js
Normal file
@ -0,0 +1,54 @@
|
||||
|
||||
/**
|
||||
## Text Editing Example
|
||||
Here, I will give a short overview on how to enable collaborative text editing with the PeerJs Connector and the TextYatta Framework.
|
||||
First you have to include the following libraries in your html file:
|
||||
```
|
||||
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
|
||||
<script src="../../build/browser/Frameworks/TextYatta.js"></script>
|
||||
<script src="../../build/browser/Connectors/PeerJsConnector.js"></script>
|
||||
<script src="./index.js"></script>
|
||||
```
|
||||
Open [index.html](./index.html) in order to start collaboration.
|
||||
*/
|
||||
var yatta;
|
||||
|
||||
function init(){
|
||||
Y.createPeerJsConnector(function(Connector, user_id){
|
||||
/**
|
||||
TextYatta is a shared text object. If you change something on this object,
|
||||
it will be instantaneously shared with all the other collaborators.
|
||||
*/
|
||||
yatta = new Y.TextYatta(user_id, Connector);
|
||||
|
||||
/*
|
||||
Get the url of this frame. If it has a url-encoded parameter
|
||||
we will connect to the foreign peer.
|
||||
*/
|
||||
var url = window.location.href;
|
||||
var peer_id = location.search
|
||||
var url = url.substring(0,-peer_id.length);
|
||||
peer_id = peer_id.substring(1);
|
||||
|
||||
/*
|
||||
Set the shareable link.
|
||||
*/
|
||||
document.getElementById("peer_link").setAttribute("href",url+"?"+user_id);
|
||||
|
||||
/*
|
||||
Connect to other peer.
|
||||
*/
|
||||
if (peer_id.length > 0){
|
||||
yatta.connector.connectToPeer(peer_id);
|
||||
}
|
||||
|
||||
/*
|
||||
Bind yatta to the textfield.
|
||||
|
||||
The .bind property is a method of the Word class. You can also use it with all the other Frameworks in Yatta (e.g. Json).
|
||||
*/
|
||||
var textbox = document.getElementById("textfield");
|
||||
yatta.bind(textbox);
|
||||
});
|
||||
}
|
||||
window.onload = init
|
@ -15,10 +15,16 @@ class TextYatta
|
||||
constructor: (user_id, Connector)->
|
||||
@HB = new HistoryBuffer user_id
|
||||
text_types = text_types_uninitialized @HB
|
||||
types = text_types.types
|
||||
@engine = new Engine @HB, text_types.parser
|
||||
@connector = new Connector @engine, @HB, text_types.execution_listener
|
||||
@connector = new Connector @engine, @HB, text_types.execution_listener, @
|
||||
|
||||
first_word = new text_types.types.Word undefined
|
||||
beginning = @HB.addOperation new types.Delimiter {creator: '_', op_number: '_beginning'} , undefined, undefined
|
||||
end = @HB.addOperation new types.Delimiter {creator: '_', op_number: '_end'} , beginning, undefined
|
||||
beginning.next_cl = end
|
||||
beginning.execute()
|
||||
end.execute()
|
||||
first_word = new text_types.types.Word {creator: '_', op_number: '_'}, beginning, end
|
||||
@HB.addOperation(first_word).execute()
|
||||
@root_element = first_word
|
||||
|
||||
@ -72,6 +78,12 @@ class TextYatta
|
||||
deleteText: (pos, length)->
|
||||
@root_element.deleteText pos, length
|
||||
|
||||
#
|
||||
# @see Word.bind
|
||||
#
|
||||
bind: (textarea)->
|
||||
@root_element.bind textarea
|
||||
|
||||
#
|
||||
# @see Word.replaceText
|
||||
#
|
||||
|
@ -330,7 +330,8 @@ module.exports = (HB)->
|
||||
while true
|
||||
if prev instanceof Delimiter
|
||||
break
|
||||
position++
|
||||
if prev.isDeleted? and not prev.isDeleted()
|
||||
position++
|
||||
prev = prev.prev_cl
|
||||
position
|
||||
#
|
||||
|
@ -146,14 +146,10 @@ module.exports = (HB)->
|
||||
|
||||
#
|
||||
# Bind this Word to a textfield.
|
||||
# TODO:
|
||||
# insert_pressing+mouse new position
|
||||
# concurrent pressing (two user pressing stuff)
|
||||
#
|
||||
bind: (textfield)->
|
||||
word = @
|
||||
textfield.value = @val()
|
||||
position_start = null
|
||||
document_length = null
|
||||
|
||||
@on "insert", (event, op)->
|
||||
if op.creator isnt HB.getUserId()
|
||||
@ -166,60 +162,74 @@ module.exports = (HB)->
|
||||
cursor
|
||||
left = fix textfield.selectionStart
|
||||
right = fix textfield.selectionEnd
|
||||
if position_start?
|
||||
document_length += 1
|
||||
position_start = fix position_start
|
||||
|
||||
textfield.value = word.val()
|
||||
textfield.setSelectionRange left, right
|
||||
|
||||
|
||||
@on "delete", (event, op)->
|
||||
if op.creator isnt HB.getUserId()
|
||||
o_pos = op.getPosition()
|
||||
fix = (cursor)->
|
||||
if cursor <= o_pos
|
||||
cursor
|
||||
else
|
||||
cursor -= 1
|
||||
cursor
|
||||
left = fix textfield.selectionStart
|
||||
right = fix textfield.selectionEnd
|
||||
if position_start?
|
||||
document_length -= 1
|
||||
position_start = fix position_start
|
||||
o_pos = op.getPosition()
|
||||
fix = (cursor)->
|
||||
if cursor < o_pos
|
||||
cursor
|
||||
else
|
||||
cursor -= 1
|
||||
cursor
|
||||
left = fix textfield.selectionStart
|
||||
right = fix textfield.selectionEnd
|
||||
|
||||
textfield.value = word.val()
|
||||
textfield.setSelectionRange left, right
|
||||
textfield.value = word.val()
|
||||
textfield.setSelectionRange left, right
|
||||
|
||||
# consume all text-insert changes.
|
||||
textfield.onkeypress = (event)->
|
||||
char = String.fromCharCode event.keyCode
|
||||
if char.length > 0
|
||||
pos = Math.min textfield.selectionStart, textfield.selectionEnd
|
||||
diff = Math.abs(textfield.selectionEnd - textfield.selectionStart)
|
||||
word.deleteText pos, diff
|
||||
word.insertText pos, char
|
||||
else
|
||||
event.preventDefault()
|
||||
|
||||
update_yatta = ()->
|
||||
if position_start?
|
||||
document_length_diff = textfield.value.length - document_length
|
||||
current_position = Math.min textfield.selectionStart, textfield.selectionEnd
|
||||
if document_length_diff < 0 # deletion
|
||||
deletion_position = Math.min current_position, position_start
|
||||
word.deleteText deletion_position, Math.abs document_length_diff
|
||||
else if document_length_diff > 0 # insertion
|
||||
text_insert = textfield.value.substring position_start, (position_start + document_length_diff)
|
||||
word.insertText position_start, text_insert
|
||||
|
||||
position_start = null
|
||||
document_length = null
|
||||
|
||||
#
|
||||
# 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 = (event)->
|
||||
#console.log "down"
|
||||
if position_start?
|
||||
update_yatta()
|
||||
pos = Math.min textfield.selectionStart, textfield.selectionEnd
|
||||
diff = Math.abs(textfield.selectionEnd - textfield.selectionStart)
|
||||
if event.keyCode? and event.keyCode is 8
|
||||
if diff > 0
|
||||
word.deleteText pos, diff
|
||||
else
|
||||
if event.ctrlKey? and event.ctrlKey
|
||||
val = textfield.value
|
||||
new_pos = pos
|
||||
del_length = 0
|
||||
if pos > 0
|
||||
new_pos--
|
||||
del_length++
|
||||
while new_pos > 0 and val[new_pos] isnt " " and val[new_pos] isnt '\n'
|
||||
new_pos--
|
||||
del_length++
|
||||
word.deleteText new_pos, (pos-new_pos)
|
||||
textfield.setSelectionRange new_pos, new_pos
|
||||
else
|
||||
word.deleteText (pos-1), 1
|
||||
event.preventDefault()
|
||||
else if event.keyCode? and event.keyCode is 46
|
||||
if diff > 0
|
||||
word.deleteText pos, diff
|
||||
else
|
||||
word.deleteText pos, 1
|
||||
event.preventDefault()
|
||||
|
||||
|
||||
selection_range = Math.abs(textfield.selectionEnd - textfield.selectionStart)
|
||||
position_start = Math.min(textfield.selectionStart, textfield.selectionEnd)
|
||||
word.deleteText position_start, selection_range
|
||||
document_length = textfield.value.length - selection_range
|
||||
|
||||
textfield.onkeyup = (event)->
|
||||
#console.log "up"
|
||||
update_yatta()
|
||||
#
|
||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||
#
|
||||
|
Loading…
x
Reference in New Issue
Block a user