rangy-rails 1.3alpha.780.0 → 1.3alpha.804.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/rangy-rails/version.rb +1 -1
- data/vendor/assets/javascripts/rangy-core.js +3731 -3
- data/vendor/assets/javascripts/rangy-cssclassapplier.js +973 -3
- data/vendor/assets/javascripts/rangy-highlighter.js +489 -3
- data/vendor/assets/javascripts/rangy-position.js +524 -15
- data/vendor/assets/javascripts/rangy-selectionsaverestore.js +223 -3
- data/vendor/assets/javascripts/rangy-serializer.js +282 -3
- data/vendor/assets/javascripts/rangy-textrange.js +1883 -3
- metadata +13 -13
@@ -26,7 +26,1887 @@
|
|
26
26
|
*
|
27
27
|
* Copyright 2013, Tim Down
|
28
28
|
* Licensed under the MIT license.
|
29
|
-
* Version: 1.3alpha.
|
30
|
-
* Build date:
|
29
|
+
* Version: 1.3alpha.804
|
30
|
+
* Build date: 8 December 2013
|
31
31
|
*/
|
32
|
-
rangy.createModule("TextRange",["WrappedSelection"],function(a,b){function t(a,b){function f(b,c,d){var f=a.slice(b,c),g={isWord:d,chars:f,toString:function(){return f.join("")}};for(var h=0,i=f.length;h<i;++h)f[h].token=g;e.push(g)}var c=a.join(""),d,e=[],g=0,h,i;while(d=b.wordRegex.exec(c)){h=d.index,i=h+d[0].length,h>g&&f(g,h,!1);if(b.includeTrailingSpace)while(m.test(a[i]))++i;f(h,i,!0),g=i}return g<a.length&&f(g,a.length,!1),e}function x(a,b){if(!a)return b;var c={};return h(c,b),h(c,a),c}function y(a){var b,c;return a?(b=a.language||o,c={},h(c,w[b]||w[o]),h(c,a),c):w[o]}function z(a){return x(a,u)}function A(a){return x(a,v)}function I(a,b){var c=F(a,"display",b),d=a.tagName.toLowerCase();return c=="block"&&G&&H.hasOwnProperty(d)?H[d]:c}function J(a){var b=P(a);for(var c=0,d=b.length;c<d;++c)if(b[c].nodeType==1&&I(b[c])=="none")return!0;return!1}function K(a){var b;return a.nodeType==3&&(b=a.parentNode)&&F(b,"visibility")=="hidden"}function L(a){return a&&(a.nodeType==1&&!/^(inline(-block|-table)?|none)$/.test(I(a))||a.nodeType==9||a.nodeType==11)}function M(a){var b=a.lastChild;return b?M(b):a}function N(a){return f.isCharacterDataNode(a)||!/^(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)$/i.test(a.nodeName)}function O(a){var b=[];while(a.parentNode)b.unshift(a.parentNode),a=a.parentNode;return b}function P(a){return O(a).concat([a])}function Q(a){var b;return typeof (b=a.namespaceURI)==c||b===null||b=="http://www.w3.org/1999/xhtml"}function R(a,b){if(!a||a.nodeType!=1||!Q(a))return!1;switch(typeof b){case"string":return a.tagName.toLowerCase()==b.toLowerCase();case"object":return(new RegExp("^("+b.join("|S")+")$","i")).test(a.tagName);default:return!0}}function S(a){while(a&&!a.nextSibling)a=a.parentNode;return a?a.nextSibling:null}function T(a,b){return!b&&a.hasChildNodes()?a.firstChild:S(a)}function U(a){var b=a.previousSibling;if(b){a=b;while(a.hasChildNodes())a=a.lastChild;return a}var c=a.parentNode;return c&&c.nodeType==1?c:null}function V(a){if(!a||a.nodeType!=3)return!1;var b=a.data;if(b==="")return!0;var c=a.parentNode;if(!c||c.nodeType!=1)return!1;var d=F(a.parentNode,"whiteSpace");return/^[\t\n\r ]+$/.test(b)&&/^(normal|nowrap)$/.test(d)||/^[\t\r ]+$/.test(b)&&d=="pre-line"}function W(a){if(a.data==="")return!0;if(!V(a))return!1;var b=a.parentNode;return b?J(a)?!0:!1:!0}function X(a){var b=a.nodeType;return b==7||b==8||J(a)||/^(script|style)$/i.test(a.nodeName)||K(a)||W(a)}function Y(a,b){var c=a.nodeType;return c==7||c==8||c==1&&I(a,b)=="none"}function Z(){this.store={}}function ab(a,b,c){return function(d){var e=this.cache;if(e.hasOwnProperty(a))return $++,e[a];_++;var f=b.call(this,c?this[c]:this,d);return e[a]=f,f}}function bb(a,b){this.node=a,this.session=b,this.cache=new Z,this.positions=new Z}function kb(a,b){this.offset=b,this.nodeWrapper=a,this.node=a.node,this.session=a.session,this.cache=new Z}function lb(){return"[Position("+f.inspectNode(this.node)+":"+this.offset+")]"}function pb(){return rb(),nb=new ob}function qb(){return nb||pb()}function rb(){nb&&nb.detach(),nb=null}function sb(a,c,d,e){function h(){var a=null,b=null;return c?(b=f,g||(f=f.previousVisible(),g=!f||d&&f.equals(d))):g||(b=f=f.nextVisible(),g=!f||d&&f.equals(d)),g&&(f=null),b}d&&(c?X(d.node)&&(d=a.previousVisible()):X(d.node)&&(d=d.nextVisible()));var f=a,g=!1,i,j=!1;return{next:function(){if(j)return j=!1,i;var a,b;while(a=h()){b=a.getCharacter(e);if(b)return i=a,a}return null},rewind:function(){if(!i)throw b.createError("createCharacterIterator: cannot rewind. Only one position can be rewound.");j=!0},dispose:function(){a=d=null}}}function ub(a,b,c){function g(a){var b,c,f=[],g=a?d:e,h=!1,i=!1;while(b=g.next()){c=b.character;if(l.test(c))i&&(i=!1,h=!0);else{if(h){g.rewind();break}i=!0}f.push(b)}return f}function n(a){var b=["["+a.length+"]"];for(var c=0;c<a.length;++c)b.push("(word: "+a[c]+", is word: "+a[c].isWord+")");return b}var d=sb(a,!1,null,b),e=sb(a,!0,null,b),f=c.tokenizer,h=g(!0),i=g(!1).reverse(),j=f(i.concat(h),c),k=h.length?j.slice(tb(j,h[0].token)):[],m=i.length?j.slice(0,tb(j,i.pop().token)+1):[];return{nextEndToken:function(){var a,b;while(k.length==1&&!(a=k[0]).isWord&&(b=g(!0)).length>0)k=f(a.chars.concat(b),c);return k.shift()},previousStartToken:function(){var a,b;while(m.length==1&&!(a=m[0]).isWord&&(b=g(!1)).length>0)m=f(b.reverse().concat(a.chars),c);return m.pop()},dispose:function(){d.dispose(),e.dispose(),k=m=null}}}function vb(a,b,c,f,g){var h=0,i,j=a,k,l,m=Math.abs(c),n;if(c!==0){var o=c<0;switch(b){case d:k=sb(a,o,null,f);while((i=k.next())&&h<m)++h,j=i;l=i,k.dispose();break;case e:var p=ub(a,f,g),q=o?p.previousStartToken:p.nextEndToken;while((n=q())&&h<m)n.isWord&&(++h,j=o?n.chars[0]:n.chars[n.chars.length-1]);break;default:throw new Error("movePositionBy: unit '"+b+"' not implemented")}o?(j=j.previousVisible(),h=-h):j&&j.isLeadingSpace&&(b==e&&(k=sb(a,!1,null,f),l=k.next(),k.dispose()),l&&(j=l.previousVisible()))}return{position:j,unitsMoved:h}}function wb(a,b,c,d){var e=a.getRangeBoundaryPosition(b,!0),f=a.getRangeBoundaryPosition(b,!1),g=d?f:e,h=d?e:f;return sb(g,!!d,h,c)}function xb(a,b,c){var d=[],e=wb(a,b,c),f;while(f=e.next())d.push(f);return e.dispose(),d}function yb(b,c,d){var e=a.createRange(b.node);e.setStartAndEnd(b.node,b.offset,c.node,c.offset);var f=!e.expand("word",d);return e.detach(),f}function zb(a,b,c,d,e){function r(a,b){var c=i[a].previousVisible(),d=i[b-1],f=!e.wholeWordsOnly||yb(c,d,e.wordOptions);return{startPos:c,endPos:d,valid:f}}var f=p(e.direction),g=sb(a,f,a.session.getRangeBoundaryPosition(d,f),e),h="",i=[],j,k,l,m,n,o,q=null;while(j=g.next()){k=j.character,!c&&!e.caseSensitive&&(k=k.toLowerCase()),f?(i.unshift(j),h=k+h):(i.push(j),h+=k);if(c){n=b.exec(h);if(n)if(o){l=n.index,m=l+n[0].length;if(!f&&m<h.length||f&&l>0){q=r(l,m);break}}else o=!0}else if((l=h.indexOf(b))!=-1){q=r(l,l+b.length);break}}return o&&(q=r(l,m)),g.dispose(),q}function Ab(a){return function(){var b=!!nb,c=qb(),d=[c].concat(g.toArray(arguments)),e=a.apply(this,d);return b||rb(),e}}function Bb(a,b){return Ab(function(c,e,f,g){typeof f=="undefined"&&(f=e,e=d),g=x(g,C);var h=z(g.characterOptions),i=y(g.wordOptions),j=a;b&&(j=f>=0,this.collapse(!j));var k=vb(c.getRangeBoundaryPosition(this,j),e,f,h,i),l=k.position;return this[j?"setStart":"setEnd"](l.node,l.offset),k.unitsMoved})}function Cb(a){return Ab(function(b,c){c=z(c);var d,e=wb(b,this,c,!a),f=0;while((d=e.next())&&l.test(d.character))++f;e.dispose();var g=f>0;return g&&this[a?"moveStart":"moveEnd"]("character",a?f:-f,{characterOptions:c}),g})}function Db(a){return Ab(function(b,c){var d=!1;return this.changeEachRange(function(b){d=b[a](c)||d}),d})}var c="undefined",d="character",e="word",f=a.dom,g=a.util,h=g.extend,i=f.getBody,j=/^[ \t\f\r\n]+$/,k=/^[ \t\f\r]+$/,l=/^[\t-\r \u0085\u00A0\u1680\u180E\u2000-\u200B\u2028\u2029\u202F\u205F\u3000]+$/,m=/^[\t \u00A0\u1680\u180E\u2000-\u200B\u202F\u205F\u3000]+$/,n=/^[\n-\r\u0085\u2028\u2029]$/,o="en",p=a.Selection.isDirectionBackward,q=!1,r=!1,s=!0;(function(){var b=document.createElement("div");b.contentEditable="true",b.innerHTML="<p>1 </p><p></p>";var c=i(document),d=b.firstChild,e=a.getSelection();c.appendChild(b),e.collapse(d.lastChild,2),e.setStart(d.firstChild,0),q=(""+e).length==1,b.innerHTML="1 <br>",e.collapse(b,2),e.setStart(b.firstChild,0),r=(""+e).length==1,c.removeChild(b),e.removeAllRanges()})();var u={includeBlockContentTrailingSpace:!0,includeSpaceBeforeBr:!0,includePreLineTrailingSpace:!0},v={includeBlockContentTrailingSpace:!s,includeSpaceBeforeBr:!r,includePreLineTrailingSpace:!0},w={en:{wordRegex:/[a-z0-9]+('[a-z0-9]+)*/gi,includeTrailingSpace:!1,tokenizer:t}},B={caseSensitive:!1,withinRange:null,wholeWordsOnly:!1,wrap:!1,direction:"forward",wordOptions:null,characterOptions:null},C={wordOptions:null,characterOptions:null},D={wordOptions:null,characterOptions:null,trim:!1,trimStart:!0,trimEnd:!0},E={wordOptions:null,characterOptions:null,direction:"forward"},F=f.getComputedStyleProperty,G;(function(){var a=document.createElement("table"),b=i(document);b.appendChild(a),G=F(a,"display")=="block",b.removeChild(a)})(),a.features.tableCssDisplayBlock=G;var H={table:"table",caption:"table-caption",colgroup:"table-column-group",col:"table-column",thead:"table-header-group",tbody:"table-row-group",tfoot:"table-footer-group",tr:"table-row",td:"table-cell",th:"table-cell"};Z.prototype={get:function(a){return this.store.hasOwnProperty(a)?this.store[a]:null},set:function(a,b){return this.store[a]=b}};var $=0,_=0;a.report=function(){console.log("Cached: "+$+", uncached: "+_)};var cb={getPosition:function(a){var b=this.positions;return b.get(a)||b.set(a,new kb(this,a))},toString:function(){return"[NodeWrapper("+f.inspectNode(this.node)+")]"}};bb.prototype=cb;var db="EMPTY",eb="NON_SPACE",fb="UNCOLLAPSIBLE_SPACE",gb="COLLAPSIBLE_SPACE",hb="TRAILING_SPACE_IN_BLOCK",ib="TRAILING_SPACE_BEFORE_BR",jb="PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK";h(cb,{isCharacterDataNode:ab("isCharacterDataNode",f.isCharacterDataNode,"node"),getNodeIndex:ab("nodeIndex",f.getNodeIndex,"node"),getLength:ab("nodeLength",f.getNodeLength,"node"),containsPositions:ab("containsPositions",N,"node"),isWhitespace:ab("isWhitespace",V,"node"),isCollapsedWhitespace:ab("isCollapsedWhitespace",W,"node"),getComputedDisplay:ab("computedDisplay",I,"node"),isCollapsed:ab("collapsed",X,"node"),isIgnored:ab("ignored",Y,"node"),next:ab("nextPos",T,"node"),previous:ab("previous",U,"node"),getTextNodeInfo:ab("textNodeInfo",function(a){var b=null,c=!1,d=F(a.parentNode,"whiteSpace"),e=d=="pre-line";if(e)b=k,c=!0;else if(d=="normal"||d=="nowrap")b=j,c=!0;return{node:a,text:a.data,spaceRegex:b,collapseSpaces:c,preLine:e}},"node"),hasInnerText:ab("hasInnerText",function(a,b){var c=this.session,d=c.getPosition(a.parentNode,this.getNodeIndex()+1),e=c.getPosition(a,0),f=b?d:e,g=b?e:d;while(f!==g){f.prepopulateChar();if(f.isDefinitelyNonEmpty())return!0;f=b?f.previousVisible():f.nextVisible()}return!1},"node"),getTrailingSpace:ab("trailingSpace",function(a){if(a.tagName.toLowerCase()=="br")return"";switch(this.getComputedDisplay()){case"inline":var b=a.lastChild;while(b){if(!Y(b))return b.nodeType==1?this.session.getNodeWrapper(b).getTrailingSpace():"";b=b.previousSibling}break;case"inline-block":case"inline-table":case"none":case"table-column":case"table-column-group":break;case"table-cell":return" ";default:return this.hasInnerText(!0)?"\n":""}return""},"node"),getLeadingSpace:ab("leadingSpace",function(a){switch(this.getComputedDisplay()){case"inline":case"inline-block":case"inline-table":case"none":case"table-column":case"table-column-group":case"table-cell":break;default:return this.hasInnerText(!1)?"\n":""}return""},"node")});var mb={character:"",characterType:db,isBr:!1,prepopulateChar:function(){var a=this;if(!a.prepopulatedChar){var b=a.node,c=a.offset,d="",e=db,f=!1;if(c>0)if(b.nodeType==3){var g=b.data,h=g.charAt(c-1),i=a.nodeWrapper.getTextNodeInfo(),j=i.spaceRegex;i.collapseSpaces?j.test(h)?c>1&&j.test(g.charAt(c-2))||(i.preLine&&g.charAt(c)==="\n"?(d=" ",e=jb):(d=" ",e=gb)):(d=h,e=eb,f=!0):(d=h,e=fb,f=!0)}else{var k=b.childNodes[c-1];k&&k.nodeType==1&&!X(k)&&(k.tagName.toLowerCase()=="br"?(d="\n",a.isBr=!0,e=gb,f=!1):a.checkForTrailingSpace=!0);if(!d){var l=b.childNodes[c];l&&l.nodeType==1&&!X(l)&&(a.checkForLeadingSpace=!0)}}a.prepopulatedChar=!0,a.character=d,a.characterType=e,a.isCharInvariant=f}},isDefinitelyNonEmpty:function(){var a=this.characterType;return a==eb||a==fb},resolveLeadingAndTrailingSpaces:function(){this.prepopulatedChar||this.prepopulateChar();if(this.checkForTrailingSpace){var a=this.session.getNodeWrapper(this.node.childNodes[this.offset-1]).getTrailingSpace();a&&(this.isTrailingSpace=!0,this.character=a,this.characterType=gb),this.checkForTrailingSpace=!1}if(this.checkForLeadingSpace){var b=this.session.getNodeWrapper(this.node.childNodes[this.offset]).getLeadingSpace();b&&(this.isLeadingSpace=!0,this.character=b,this.characterType=gb),this.checkForLeadingSpace=!1}},getPrecedingUncollapsedPosition:function(a){var b=this,c;while(b=b.previousVisible()){c=b.getCharacter(a);if(c!=="")return b}return null},getCharacter:function(a){function j(){return h||(g=i.getPrecedingUncollapsedPosition(a),h=!0),g}this.resolveLeadingAndTrailingSpaces();if(this.isCharInvariant)return this.character;var b=["character",a.includeSpaceBeforeBr,a.includeBlockContentTrailingSpace,a.includePreLineTrailingSpace].join("_"),c=this.cache.get(b);if(c!==null)return c;var d="",e=this.characterType==gb,f,g,h=!1,i=this;if(e){if(this.character!=" "||!!j()&&!g.isTrailingSpace&&g.character!="\n")if(this.character=="\n"&&this.isLeadingSpace)j()&&g.character!="\n"&&(d="\n");else{f=this.nextUncollapsed();if(f){f.isBr?this.type=ib:f.isTrailingSpace&&f.character=="\n"&&(this.type=hb);if(f.character==="\n"){if(this.type!=ib||!!a.includeSpaceBeforeBr)if(this.type!=hb||!f.isTrailingSpace||!!a.includeBlockContentTrailingSpace)if(this.type!=jb||f.type!=eb||!!a.includePreLineTrailingSpace)this.character==="\n"?f.isTrailingSpace?this.isTrailingSpace||!this.isBr:d="\n":this.character===" "&&(d=" ")}else d=this.character}}}else this.character!=="\n"||!!(f=this.nextUncollapsed())&&!f.isTrailingSpace;return this.cache.set(b,d),d},equals:function(a){return!!a&&this.node===a.node&&this.offset===a.offset},inspect:lb,toString:function(){return this.character}};kb.prototype=mb,h(mb,{next:ab("nextPos",function(a){var b=a.nodeWrapper,c=a.node,d=a.offset,e=b.session;if(!c)return null;var f,g,h;return d==b.getLength()?(f=c.parentNode,g=f?b.getNodeIndex()+1:0):b.isCharacterDataNode()?(f=c,g=d+1):(h=c.childNodes[d],e.getNodeWrapper(h).containsPositions()?(f=h,g=0):(f=c,g=d+1)),f?e.getPosition(f,g):null}),previous:ab("previous",function(a){var b=a.nodeWrapper,c=a.node,d=a.offset,e=b.session,g,h,i;return d==0?(g=c.parentNode,h=g?b.getNodeIndex():0):b.isCharacterDataNode()?(g=c,h=d-1):(i=c.childNodes[d-1],e.getNodeWrapper(i).containsPositions()?(g=i,h=f.getNodeLength(i)):(g=c,h=d-1)),g?e.getPosition(g,h):null}),nextVisible:ab("nextVisible",function(a){var b=a.next();if(!b)return null;var c=b.nodeWrapper,d=b.node,e=b;return c.isCollapsed()&&(e=c.session.getPosition(d.parentNode,c.getNodeIndex()+1)),e}),nextUncollapsed:ab("nextUncollapsed",function(a){var b=a;while(b=b.nextVisible()){b.resolveLeadingAndTrailingSpaces();if(b.character!=="")return b}return null}),previousVisible:ab("previousVisible",function(a){var b=a.previous();if(!b)return null;var c=b.nodeWrapper,d=b.node,e=b;return c.isCollapsed()&&(e=c.session.getPosition(d.parentNode,c.getNodeIndex())),e})});var nb=null,ob=function(){function a(a){var b=new Z;return{get:function(c){var d=b.get(c[a]);if(d)for(var e=0,f;f=d[e++];)if(f.node===c)return f;return null},set:function(c){var d=c.node[a],e=b.get(d)||b.set(d,[]);e.push(c)}}}function c(){this.initCaches()}var b=g.isHostProperty(document.documentElement,"uniqueID");return c.prototype={initCaches:function(){this.elementCache=b?function(){var a=new Z;return{get:function(b){return a.get(b.uniqueID)},set:function(b){a.set(b.node.uniqueID,b)}}}():a("tagName"),this.textNodeCache=a("data"),this.otherNodeCache=a("nodeName")},getNodeWrapper:function(a){var b;switch(a.nodeType){case 1:b=this.elementCache;break;case 3:b=this.textNodeCache;break;default:b=this.otherNodeCache}var c=b.get(a);return c||(c=new bb(a,this),b.set(c)),c},getPosition:function(a,b){return this.getNodeWrapper(a).getPosition(b)},getRangeBoundaryPosition:function(a,b){var c=b?"start":"end";return this.getPosition(a[c+"Container"],a[c+"Offset"])},detach:function(){this.elementCache=this.textNodeCache=this.otherNodeCache=null}},c}();h(f,{nextNode:T,previousNode:U});var tb=Array.prototype.indexOf?function(a,b){return a.indexOf(b)}:function(a,b){for(var c=0,d=a.length;c<d;++c)if(a[c]===b)return c;return-1};h(a.rangePrototype,{moveStart:Bb(!0,!1),moveEnd:Bb(!1,!1),move:Bb(!0,!0),trimStart:Cb(!0),trimEnd:Cb(!1),trim:Ab(function(a,b){var c=this.trimStart(b),d=this.trimEnd(b);return c||d}),expand:Ab(function(a,b,c){var f=!1;c=x(c,D);var g=z(c.characterOptions);b||(b=d);if(b==e){var h=y(c.wordOptions),i=a.getRangeBoundaryPosition(this,!0),j=a.getRangeBoundaryPosition(this,!1),k=ub(i,g,h),l=k.nextEndToken(),m=l.chars[0].previousVisible(),n,o;if(this.collapsed)n=l;else{var p=ub(j,g,h);n=p.previousStartToken()}return o=n.chars[n.chars.length-1],m.equals(i)||(this.setStart(m.node,m.offset),f=!0),o&&!o.equals(j)&&(this.setEnd(o.node,o.offset),f=!0),c.trim&&(c.trimStart&&(f=this.trimStart(g)||f),c.trimEnd&&(f=this.trimEnd(g)||f)),f}return this.moveEnd(d,1,c)}),text:Ab(function(a,b){return this.collapsed?"":xb(a,this,z(b)).join("")}),selectCharacters:Ab(function(a,b,c,d,e){var f={characterOptions:e};b||(b=i(this.getDocument())),this.selectNodeContents(b),this.collapse(!0),this.moveStart("character",c,f),this.collapse(!0),this.moveEnd("character",d-c,f)}),toCharacterRange:Ab(function(a,b,c){b||(b=i(this.getDocument()));var d=b.parentNode,e=f.getNodeIndex(b),g=f.comparePoints(this.startContainer,this.endContainer,d,e)==-1,h=this.cloneRange(),j,k;return g?(h.setStartAndEnd(this.startContainer,this.startOffset,d,e),j=-h.text(c).length):(h.setStartAndEnd(d,e,this.startContainer,this.startOffset),j=h.text(c).length),k=j+this.text(c).length,{start:j,end:k}}),findText:Ab(function(b,c,d){d=x(d,B),d.wholeWordsOnly&&(d.wordOptions=y(d.wordOptions),d.wordOptions.includeTrailingSpace=!1);var e=p(d.direction),f=d.withinRange;f||(f=a.createRange(),f.selectNodeContents(this.getDocument()));var g=c,h=!1;typeof g=="string"?d.caseSensitive||(g=g.toLowerCase()):h=!0;var i=b.getRangeBoundaryPosition(this,!e),j=f.comparePoint(i.node,i.offset);j===-1?i=b.getRangeBoundaryPosition(f,!0):j===1&&(i=b.getRangeBoundaryPosition(f,!1));var k=i,l=!1,m;for(;;){m=zb(k,g,h,f,d);if(m){if(m.valid)return this.setStartAndEnd(m.startPos.node,m.startPos.offset,m.endPos.node,m.endPos.offset),!0;k=e?m.startPos:m.endPos}else{if(!d.wrap||!!l)return!1;f=f.cloneRange(),k=b.getRangeBoundaryPosition(f,!e),f.setBoundary(i.node,i.offset,e),l=!0}}}),pasteHtml:function(a){this.deleteContents();if(a){var b=this.createContextualFragment(a),c=b.lastChild;this.insertNode(b),this.collapseAfter(c)}}}),h(a.selectionPrototype,{expand:Ab(function(a,b,c){this.changeEachRange(function(a){a.expand(b,c)})}),move:Ab(function(a,b,c,d){var e=0;if(this.focusNode){this.collapse(this.focusNode,this.focusOffset);var f=this.getRangeAt(0);d||(d={}),d.characterOptions=A(d.characterOptions),e=f.move(b,c,d),this.setSingleRange(f)}return e}),trimStart:Db("trimStart"),trimEnd:Db("trimEnd"),trim:Db("trim"),selectCharacters:Ab(function(b,c,d,e,f,g){var h=a.createRange(c);h.selectCharacters(c,d,e,g),this.setSingleRange(h,f)}),saveCharacterRanges:Ab(function(a,b,c){var d=this.getAllRanges(),e=d.length,f=[],g=e==1&&this.isBackward();for(var h=0,i=d.length;h<i;++h)f[h]={characterRange:d[h].toCharacterRange(b,c),backward:g,characterOptions:c};return f}),restoreCharacterRanges:Ab(function(b,c,d){this.removeAllRanges();for(var e=0,f=d.length,g,h,i;e<f;++e)h=d[e],i=h.characterRange,g=a.createRange(c),g.selectCharacters(c,i.start,i.end,h.characterOptions),this.addRange(g,h.backward)}),text:Ab(function(a,b){var c=[];for(var d=0,e=this.rangeCount;d<e;++d)c[d]=this.getRangeAt(d).text(b);return c.join("")})}),a.innerText=function(b,c){var d=a.createRange(b);d.selectNodeContents(b);var e=d.text(c);return d.detach(),e},a.createWordIterator=function(a,b,c){var d=qb();c=x(c,E);var e=z(c.characterOptions),f=y(c.wordOptions),g=d.getPosition(a,b),h=ub(g,e,f),i=p(c.direction);return{next:function(){return i?h.previousStartToken():h.nextEndToken()},dispose:function(){h.dispose(),this.next=function(){}}}},a.noMutation=function(a){var b=qb();a(b),rb()},a.noMutation.createEntryPointFunction=Ab,a.textRange={isBlockNode:L,isCollapsedWhitespaceNode:W,createPosition:Ab(function(a,b,c){return a.getPosition(b,c)})}})
|
32
|
+
|
33
|
+
/**
|
34
|
+
* Problem: handling of trailing spaces before line breaks is handled inconsistently between browsers.
|
35
|
+
*
|
36
|
+
* First, a <br>: this is relatively simple. For the following HTML:
|
37
|
+
*
|
38
|
+
* 1 <br>2
|
39
|
+
*
|
40
|
+
* - IE and WebKit render the space, include it in the selection (i.e. when the content is selected and pasted into a
|
41
|
+
* textarea, the space is present) and allow the caret to be placed after it.
|
42
|
+
* - Firefox does not acknowledge the space in the selection but it is possible to place the caret after it.
|
43
|
+
* - Opera does not render the space but has two separate caret positions on either side of the space (left and right
|
44
|
+
* arrow keys show this) and includes the space in the selection.
|
45
|
+
*
|
46
|
+
* The other case is the line break or breaks implied by block elements. For the following HTML:
|
47
|
+
*
|
48
|
+
* <p>1 </p><p>2<p>
|
49
|
+
*
|
50
|
+
* - WebKit does not acknowledge the space in any way
|
51
|
+
* - Firefox, IE and Opera as per <br>
|
52
|
+
*
|
53
|
+
* One more case is trailing spaces before line breaks in elements with white-space: pre-line. For the following HTML:
|
54
|
+
*
|
55
|
+
* <p style="white-space: pre-line">1
|
56
|
+
* 2</p>
|
57
|
+
*
|
58
|
+
* - Firefox and WebKit include the space in caret positions
|
59
|
+
* - IE does not support pre-line up to and including version 9
|
60
|
+
* - Opera ignores the space
|
61
|
+
* - Trailing space only renders if there is a non-collapsed character in the line
|
62
|
+
*
|
63
|
+
* Problem is whether Rangy should ever acknowledge the space and if so, when. Another problem is whether this can be
|
64
|
+
* feature-tested
|
65
|
+
*/
|
66
|
+
rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) {
|
67
|
+
var UNDEF = "undefined";
|
68
|
+
var CHARACTER = "character", WORD = "word";
|
69
|
+
var dom = api.dom, util = api.util;
|
70
|
+
var extend = util.extend;
|
71
|
+
var getBody = dom.getBody;
|
72
|
+
|
73
|
+
|
74
|
+
var spacesRegex = /^[ \t\f\r\n]+$/;
|
75
|
+
var spacesMinusLineBreaksRegex = /^[ \t\f\r]+$/;
|
76
|
+
var allWhiteSpaceRegex = /^[\t-\r \u0085\u00A0\u1680\u180E\u2000-\u200B\u2028\u2029\u202F\u205F\u3000]+$/;
|
77
|
+
var nonLineBreakWhiteSpaceRegex = /^[\t \u00A0\u1680\u180E\u2000-\u200B\u202F\u205F\u3000]+$/;
|
78
|
+
var lineBreakRegex = /^[\n-\r\u0085\u2028\u2029]$/;
|
79
|
+
|
80
|
+
var defaultLanguage = "en";
|
81
|
+
|
82
|
+
var isDirectionBackward = api.Selection.isDirectionBackward;
|
83
|
+
|
84
|
+
// Properties representing whether trailing spaces inside blocks are completely collapsed (as they are in WebKit,
|
85
|
+
// but not other browsers). Also test whether trailing spaces before <br> elements are collapsed.
|
86
|
+
var trailingSpaceInBlockCollapses = false;
|
87
|
+
var trailingSpaceBeforeBrCollapses = false;
|
88
|
+
var trailingSpaceBeforeBlockCollapses = false;
|
89
|
+
var trailingSpaceBeforeLineBreakInPreLineCollapses = true;
|
90
|
+
|
91
|
+
(function() {
|
92
|
+
var el = document.createElement("div");
|
93
|
+
el.contentEditable = "true";
|
94
|
+
el.innerHTML = "<p>1 </p><p></p>";
|
95
|
+
var body = getBody(document);
|
96
|
+
var p = el.firstChild;
|
97
|
+
var sel = api.getSelection();
|
98
|
+
|
99
|
+
body.appendChild(el);
|
100
|
+
sel.collapse(p.lastChild, 2);
|
101
|
+
sel.setStart(p.firstChild, 0);
|
102
|
+
trailingSpaceInBlockCollapses = ("" + sel).length == 1;
|
103
|
+
|
104
|
+
el.innerHTML = "1 <br>";
|
105
|
+
sel.collapse(el, 2);
|
106
|
+
sel.setStart(el.firstChild, 0);
|
107
|
+
trailingSpaceBeforeBrCollapses = ("" + sel).length == 1;
|
108
|
+
|
109
|
+
el.innerHTML = "1 <p>1</p>";
|
110
|
+
sel.collapse(el, 2);
|
111
|
+
sel.setStart(el.firstChild, 0);
|
112
|
+
trailingSpaceBeforeBlockCollapses = ("" + sel).length == 1;
|
113
|
+
|
114
|
+
body.removeChild(el);
|
115
|
+
sel.removeAllRanges();
|
116
|
+
})();
|
117
|
+
|
118
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
119
|
+
|
120
|
+
// This function must create word and non-word tokens for the whole of the text supplied to it
|
121
|
+
function defaultTokenizer(chars, wordOptions) {
|
122
|
+
var word = chars.join(""), result, tokens = [];
|
123
|
+
|
124
|
+
function createTokenFromRange(start, end, isWord) {
|
125
|
+
var tokenChars = chars.slice(start, end);
|
126
|
+
var token = {
|
127
|
+
isWord: isWord,
|
128
|
+
chars: tokenChars,
|
129
|
+
toString: function() {
|
130
|
+
return tokenChars.join("");
|
131
|
+
}
|
132
|
+
};
|
133
|
+
for (var i = 0, len = tokenChars.length; i < len; ++i) {
|
134
|
+
tokenChars[i].token = token;
|
135
|
+
}
|
136
|
+
tokens.push(token);
|
137
|
+
}
|
138
|
+
|
139
|
+
// Match words and mark characters
|
140
|
+
var lastWordEnd = 0, wordStart, wordEnd;
|
141
|
+
while ( (result = wordOptions.wordRegex.exec(word)) ) {
|
142
|
+
wordStart = result.index;
|
143
|
+
wordEnd = wordStart + result[0].length;
|
144
|
+
|
145
|
+
// Create token for non-word characters preceding this word
|
146
|
+
if (wordStart > lastWordEnd) {
|
147
|
+
createTokenFromRange(lastWordEnd, wordStart, false);
|
148
|
+
}
|
149
|
+
|
150
|
+
// Get trailing space characters for word
|
151
|
+
if (wordOptions.includeTrailingSpace) {
|
152
|
+
while (nonLineBreakWhiteSpaceRegex.test(chars[wordEnd])) {
|
153
|
+
++wordEnd;
|
154
|
+
}
|
155
|
+
}
|
156
|
+
createTokenFromRange(wordStart, wordEnd, true);
|
157
|
+
lastWordEnd = wordEnd;
|
158
|
+
}
|
159
|
+
|
160
|
+
// Create token for trailing non-word characters, if any exist
|
161
|
+
if (lastWordEnd < chars.length) {
|
162
|
+
createTokenFromRange(lastWordEnd, chars.length, false);
|
163
|
+
}
|
164
|
+
|
165
|
+
return tokens;
|
166
|
+
}
|
167
|
+
|
168
|
+
var defaultCharacterOptions = {
|
169
|
+
includeBlockContentTrailingSpace: true,
|
170
|
+
includeSpaceBeforeBr: true,
|
171
|
+
includeSpaceBeforeBlock: true,
|
172
|
+
includePreLineTrailingSpace: true
|
173
|
+
};
|
174
|
+
|
175
|
+
var defaultCaretCharacterOptions = {
|
176
|
+
includeBlockContentTrailingSpace: !trailingSpaceBeforeLineBreakInPreLineCollapses,
|
177
|
+
includeSpaceBeforeBr: !trailingSpaceBeforeBrCollapses,
|
178
|
+
includeSpaceBeforeBlock: !trailingSpaceBeforeBlockCollapses,
|
179
|
+
includePreLineTrailingSpace: true
|
180
|
+
};
|
181
|
+
|
182
|
+
var defaultWordOptions = {
|
183
|
+
"en": {
|
184
|
+
wordRegex: /[a-z0-9]+('[a-z0-9]+)*/gi,
|
185
|
+
includeTrailingSpace: false,
|
186
|
+
tokenizer: defaultTokenizer
|
187
|
+
}
|
188
|
+
};
|
189
|
+
|
190
|
+
function createOptions(optionsParam, defaults) {
|
191
|
+
if (!optionsParam) {
|
192
|
+
return defaults;
|
193
|
+
} else {
|
194
|
+
var options = {};
|
195
|
+
extend(options, defaults);
|
196
|
+
extend(options, optionsParam);
|
197
|
+
return options;
|
198
|
+
}
|
199
|
+
}
|
200
|
+
|
201
|
+
function createWordOptions(options) {
|
202
|
+
var lang, defaults;
|
203
|
+
if (!options) {
|
204
|
+
return defaultWordOptions[defaultLanguage];
|
205
|
+
} else {
|
206
|
+
lang = options.language || defaultLanguage;
|
207
|
+
defaults = {};
|
208
|
+
extend(defaults, defaultWordOptions[lang] || defaultWordOptions[defaultLanguage]);
|
209
|
+
extend(defaults, options);
|
210
|
+
return defaults;
|
211
|
+
}
|
212
|
+
}
|
213
|
+
|
214
|
+
function createCharacterOptions(options) {
|
215
|
+
return createOptions(options, defaultCharacterOptions);
|
216
|
+
}
|
217
|
+
|
218
|
+
function createCaretCharacterOptions(options) {
|
219
|
+
return createOptions(options, defaultCaretCharacterOptions);
|
220
|
+
}
|
221
|
+
|
222
|
+
var defaultFindOptions = {
|
223
|
+
caseSensitive: false,
|
224
|
+
withinRange: null,
|
225
|
+
wholeWordsOnly: false,
|
226
|
+
wrap: false,
|
227
|
+
direction: "forward",
|
228
|
+
wordOptions: null,
|
229
|
+
characterOptions: null
|
230
|
+
};
|
231
|
+
|
232
|
+
var defaultMoveOptions = {
|
233
|
+
wordOptions: null,
|
234
|
+
characterOptions: null
|
235
|
+
};
|
236
|
+
|
237
|
+
var defaultExpandOptions = {
|
238
|
+
wordOptions: null,
|
239
|
+
characterOptions: null,
|
240
|
+
trim: false,
|
241
|
+
trimStart: true,
|
242
|
+
trimEnd: true
|
243
|
+
};
|
244
|
+
|
245
|
+
var defaultWordIteratorOptions = {
|
246
|
+
wordOptions: null,
|
247
|
+
characterOptions: null,
|
248
|
+
direction: "forward"
|
249
|
+
};
|
250
|
+
|
251
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
252
|
+
|
253
|
+
/* DOM utility functions */
|
254
|
+
var getComputedStyleProperty = dom.getComputedStyleProperty;
|
255
|
+
|
256
|
+
// Create cachable versions of DOM functions
|
257
|
+
|
258
|
+
// Test for old IE's incorrect display properties
|
259
|
+
var tableCssDisplayBlock;
|
260
|
+
(function() {
|
261
|
+
var table = document.createElement("table");
|
262
|
+
var body = getBody(document);
|
263
|
+
body.appendChild(table);
|
264
|
+
tableCssDisplayBlock = (getComputedStyleProperty(table, "display") == "block");
|
265
|
+
body.removeChild(table);
|
266
|
+
})();
|
267
|
+
|
268
|
+
api.features.tableCssDisplayBlock = tableCssDisplayBlock;
|
269
|
+
|
270
|
+
var defaultDisplayValueForTag = {
|
271
|
+
table: "table",
|
272
|
+
caption: "table-caption",
|
273
|
+
colgroup: "table-column-group",
|
274
|
+
col: "table-column",
|
275
|
+
thead: "table-header-group",
|
276
|
+
tbody: "table-row-group",
|
277
|
+
tfoot: "table-footer-group",
|
278
|
+
tr: "table-row",
|
279
|
+
td: "table-cell",
|
280
|
+
th: "table-cell"
|
281
|
+
};
|
282
|
+
|
283
|
+
// Corrects IE's "block" value for table-related elements
|
284
|
+
function getComputedDisplay(el, win) {
|
285
|
+
var display = getComputedStyleProperty(el, "display", win);
|
286
|
+
var tagName = el.tagName.toLowerCase();
|
287
|
+
return (display == "block"
|
288
|
+
&& tableCssDisplayBlock
|
289
|
+
&& defaultDisplayValueForTag.hasOwnProperty(tagName))
|
290
|
+
? defaultDisplayValueForTag[tagName] : display;
|
291
|
+
}
|
292
|
+
|
293
|
+
function isHidden(node) {
|
294
|
+
var ancestors = getAncestorsAndSelf(node);
|
295
|
+
for (var i = 0, len = ancestors.length; i < len; ++i) {
|
296
|
+
if (ancestors[i].nodeType == 1 && getComputedDisplay(ancestors[i]) == "none") {
|
297
|
+
return true;
|
298
|
+
}
|
299
|
+
}
|
300
|
+
|
301
|
+
return false;
|
302
|
+
}
|
303
|
+
|
304
|
+
function isVisibilityHiddenTextNode(textNode) {
|
305
|
+
var el;
|
306
|
+
return textNode.nodeType == 3
|
307
|
+
&& (el = textNode.parentNode)
|
308
|
+
&& getComputedStyleProperty(el, "visibility") == "hidden";
|
309
|
+
}
|
310
|
+
|
311
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
312
|
+
|
313
|
+
|
314
|
+
// "A block node is either an Element whose "display" property does not have
|
315
|
+
// resolved value "inline" or "inline-block" or "inline-table" or "none", or a
|
316
|
+
// Document, or a DocumentFragment."
|
317
|
+
function isBlockNode(node) {
|
318
|
+
return node
|
319
|
+
&& ((node.nodeType == 1 && !/^(inline(-block|-table)?|none)$/.test(getComputedDisplay(node)))
|
320
|
+
|| node.nodeType == 9 || node.nodeType == 11);
|
321
|
+
}
|
322
|
+
|
323
|
+
function getLastDescendantOrSelf(node) {
|
324
|
+
var lastChild = node.lastChild;
|
325
|
+
return lastChild ? getLastDescendantOrSelf(lastChild) : node;
|
326
|
+
}
|
327
|
+
|
328
|
+
function containsPositions(node) {
|
329
|
+
return dom.isCharacterDataNode(node)
|
330
|
+
|| !/^(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)$/i.test(node.nodeName);
|
331
|
+
}
|
332
|
+
|
333
|
+
function getAncestors(node) {
|
334
|
+
var ancestors = [];
|
335
|
+
while (node.parentNode) {
|
336
|
+
ancestors.unshift(node.parentNode);
|
337
|
+
node = node.parentNode;
|
338
|
+
}
|
339
|
+
return ancestors;
|
340
|
+
}
|
341
|
+
|
342
|
+
function getAncestorsAndSelf(node) {
|
343
|
+
return getAncestors(node).concat([node]);
|
344
|
+
}
|
345
|
+
|
346
|
+
function nextNodeDescendants(node) {
|
347
|
+
while (node && !node.nextSibling) {
|
348
|
+
node = node.parentNode;
|
349
|
+
}
|
350
|
+
if (!node) {
|
351
|
+
return null;
|
352
|
+
}
|
353
|
+
return node.nextSibling;
|
354
|
+
}
|
355
|
+
|
356
|
+
function nextNode(node, excludeChildren) {
|
357
|
+
if (!excludeChildren && node.hasChildNodes()) {
|
358
|
+
return node.firstChild;
|
359
|
+
}
|
360
|
+
return nextNodeDescendants(node);
|
361
|
+
}
|
362
|
+
|
363
|
+
function previousNode(node) {
|
364
|
+
var previous = node.previousSibling;
|
365
|
+
if (previous) {
|
366
|
+
node = previous;
|
367
|
+
while (node.hasChildNodes()) {
|
368
|
+
node = node.lastChild;
|
369
|
+
}
|
370
|
+
return node;
|
371
|
+
}
|
372
|
+
var parent = node.parentNode;
|
373
|
+
if (parent && parent.nodeType == 1) {
|
374
|
+
return parent;
|
375
|
+
}
|
376
|
+
return null;
|
377
|
+
}
|
378
|
+
|
379
|
+
// Adpated from Aryeh's code.
|
380
|
+
// "A whitespace node is either a Text node whose data is the empty string; or
|
381
|
+
// a Text node whose data consists only of one or more tabs (0x0009), line
|
382
|
+
// feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose
|
383
|
+
// parent is an Element whose resolved value for "white-space" is "normal" or
|
384
|
+
// "nowrap"; or a Text node whose data consists only of one or more tabs
|
385
|
+
// (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose
|
386
|
+
// parent is an Element whose resolved value for "white-space" is "pre-line"."
|
387
|
+
function isWhitespaceNode(node) {
|
388
|
+
if (!node || node.nodeType != 3) {
|
389
|
+
return false;
|
390
|
+
}
|
391
|
+
var text = node.data;
|
392
|
+
if (text === "") {
|
393
|
+
return true;
|
394
|
+
}
|
395
|
+
var parent = node.parentNode;
|
396
|
+
if (!parent || parent.nodeType != 1) {
|
397
|
+
return false;
|
398
|
+
}
|
399
|
+
var computedWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
|
400
|
+
|
401
|
+
return (/^[\t\n\r ]+$/.test(text) && /^(normal|nowrap)$/.test(computedWhiteSpace))
|
402
|
+
|| (/^[\t\r ]+$/.test(text) && computedWhiteSpace == "pre-line");
|
403
|
+
}
|
404
|
+
|
405
|
+
// Adpated from Aryeh's code.
|
406
|
+
// "node is a collapsed whitespace node if the following algorithm returns
|
407
|
+
// true:"
|
408
|
+
function isCollapsedWhitespaceNode(node) {
|
409
|
+
// "If node's data is the empty string, return true."
|
410
|
+
if (node.data === "") {
|
411
|
+
return true;
|
412
|
+
}
|
413
|
+
|
414
|
+
// "If node is not a whitespace node, return false."
|
415
|
+
if (!isWhitespaceNode(node)) {
|
416
|
+
return false;
|
417
|
+
}
|
418
|
+
|
419
|
+
// "Let ancestor be node's parent."
|
420
|
+
var ancestor = node.parentNode;
|
421
|
+
|
422
|
+
// "If ancestor is null, return true."
|
423
|
+
if (!ancestor) {
|
424
|
+
return true;
|
425
|
+
}
|
426
|
+
|
427
|
+
// "If the "display" property of some ancestor of node has resolved value "none", return true."
|
428
|
+
if (isHidden(node)) {
|
429
|
+
return true;
|
430
|
+
}
|
431
|
+
|
432
|
+
return false;
|
433
|
+
}
|
434
|
+
|
435
|
+
function isCollapsedNode(node) {
|
436
|
+
var type = node.nodeType;
|
437
|
+
return type == 7 /* PROCESSING_INSTRUCTION */
|
438
|
+
|| type == 8 /* COMMENT */
|
439
|
+
|| isHidden(node)
|
440
|
+
|| /^(script|style)$/i.test(node.nodeName)
|
441
|
+
|| isVisibilityHiddenTextNode(node)
|
442
|
+
|| isCollapsedWhitespaceNode(node);
|
443
|
+
}
|
444
|
+
|
445
|
+
function isIgnoredNode(node, win) {
|
446
|
+
var type = node.nodeType;
|
447
|
+
return type == 7 /* PROCESSING_INSTRUCTION */
|
448
|
+
|| type == 8 /* COMMENT */
|
449
|
+
|| (type == 1 && getComputedDisplay(node, win) == "none");
|
450
|
+
}
|
451
|
+
|
452
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
453
|
+
|
454
|
+
// Possibly overengineered caching system to prevent repeated DOM calls slowing everything down
|
455
|
+
|
456
|
+
function Cache() {
|
457
|
+
this.store = {};
|
458
|
+
}
|
459
|
+
|
460
|
+
Cache.prototype = {
|
461
|
+
get: function(key) {
|
462
|
+
return this.store.hasOwnProperty(key) ? this.store[key] : null;
|
463
|
+
},
|
464
|
+
|
465
|
+
set: function(key, value) {
|
466
|
+
return this.store[key] = value;
|
467
|
+
}
|
468
|
+
};
|
469
|
+
|
470
|
+
var cachedCount = 0, uncachedCount = 0;
|
471
|
+
|
472
|
+
function createCachingGetter(methodName, func, objProperty) {
|
473
|
+
return function(args) {
|
474
|
+
var cache = this.cache;
|
475
|
+
if (cache.hasOwnProperty(methodName)) {
|
476
|
+
cachedCount++;
|
477
|
+
return cache[methodName];
|
478
|
+
} else {
|
479
|
+
uncachedCount++;
|
480
|
+
var value = func.call(this, objProperty ? this[objProperty] : this, args);
|
481
|
+
cache[methodName] = value;
|
482
|
+
return value;
|
483
|
+
}
|
484
|
+
};
|
485
|
+
}
|
486
|
+
|
487
|
+
/*
|
488
|
+
api.report = function() {
|
489
|
+
console.log("Cached: " + cachedCount + ", uncached: " + uncachedCount);
|
490
|
+
};
|
491
|
+
*/
|
492
|
+
|
493
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
494
|
+
|
495
|
+
function NodeWrapper(node, session) {
|
496
|
+
this.node = node;
|
497
|
+
this.session = session;
|
498
|
+
this.cache = new Cache();
|
499
|
+
this.positions = new Cache();
|
500
|
+
}
|
501
|
+
|
502
|
+
var nodeProto = {
|
503
|
+
getPosition: function(offset) {
|
504
|
+
var positions = this.positions;
|
505
|
+
return positions.get(offset) || positions.set(offset, new Position(this, offset));
|
506
|
+
},
|
507
|
+
|
508
|
+
toString: function() {
|
509
|
+
return "[NodeWrapper(" + dom.inspectNode(this.node) + ")]";
|
510
|
+
}
|
511
|
+
};
|
512
|
+
|
513
|
+
NodeWrapper.prototype = nodeProto;
|
514
|
+
|
515
|
+
var EMPTY = "EMPTY",
|
516
|
+
NON_SPACE = "NON_SPACE",
|
517
|
+
UNCOLLAPSIBLE_SPACE = "UNCOLLAPSIBLE_SPACE",
|
518
|
+
COLLAPSIBLE_SPACE = "COLLAPSIBLE_SPACE",
|
519
|
+
TRAILING_SPACE_BEFORE_BLOCK = "TRAILING_SPACE_BEFORE_BLOCK",
|
520
|
+
TRAILING_SPACE_IN_BLOCK = "TRAILING_SPACE_IN_BLOCK",
|
521
|
+
TRAILING_SPACE_BEFORE_BR = "TRAILING_SPACE_BEFORE_BR",
|
522
|
+
PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK = "PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK",
|
523
|
+
TRAILING_LINE_BREAK_AFTER_BR = "TRAILING_LINE_BREAK_AFTER_BR";
|
524
|
+
|
525
|
+
extend(nodeProto, {
|
526
|
+
isCharacterDataNode: createCachingGetter("isCharacterDataNode", dom.isCharacterDataNode, "node"),
|
527
|
+
getNodeIndex: createCachingGetter("nodeIndex", dom.getNodeIndex, "node"),
|
528
|
+
getLength: createCachingGetter("nodeLength", dom.getNodeLength, "node"),
|
529
|
+
containsPositions: createCachingGetter("containsPositions", containsPositions, "node"),
|
530
|
+
isWhitespace: createCachingGetter("isWhitespace", isWhitespaceNode, "node"),
|
531
|
+
isCollapsedWhitespace: createCachingGetter("isCollapsedWhitespace", isCollapsedWhitespaceNode, "node"),
|
532
|
+
getComputedDisplay: createCachingGetter("computedDisplay", getComputedDisplay, "node"),
|
533
|
+
isCollapsed: createCachingGetter("collapsed", isCollapsedNode, "node"),
|
534
|
+
isIgnored: createCachingGetter("ignored", isIgnoredNode, "node"),
|
535
|
+
next: createCachingGetter("nextPos", nextNode, "node"),
|
536
|
+
previous: createCachingGetter("previous", previousNode, "node"),
|
537
|
+
|
538
|
+
getTextNodeInfo: createCachingGetter("textNodeInfo", function(textNode) {
|
539
|
+
var spaceRegex = null, collapseSpaces = false;
|
540
|
+
var cssWhitespace = getComputedStyleProperty(textNode.parentNode, "whiteSpace");
|
541
|
+
var preLine = (cssWhitespace == "pre-line");
|
542
|
+
if (preLine) {
|
543
|
+
spaceRegex = spacesMinusLineBreaksRegex;
|
544
|
+
collapseSpaces = true;
|
545
|
+
} else if (cssWhitespace == "normal" || cssWhitespace == "nowrap") {
|
546
|
+
spaceRegex = spacesRegex;
|
547
|
+
collapseSpaces = true;
|
548
|
+
}
|
549
|
+
|
550
|
+
return {
|
551
|
+
node: textNode,
|
552
|
+
text: textNode.data,
|
553
|
+
spaceRegex: spaceRegex,
|
554
|
+
collapseSpaces: collapseSpaces,
|
555
|
+
preLine: preLine
|
556
|
+
};
|
557
|
+
}, "node"),
|
558
|
+
|
559
|
+
hasInnerText: createCachingGetter("hasInnerText", function(el, backward) {
|
560
|
+
var session = this.session;
|
561
|
+
var posAfterEl = session.getPosition(el.parentNode, this.getNodeIndex() + 1);
|
562
|
+
var firstPosInEl = session.getPosition(el, 0);
|
563
|
+
|
564
|
+
var pos = backward ? posAfterEl : firstPosInEl;
|
565
|
+
var endPos = backward ? firstPosInEl : posAfterEl;
|
566
|
+
|
567
|
+
/*
|
568
|
+
<body><p>X </p><p>Y</p></body>
|
569
|
+
|
570
|
+
Positions:
|
571
|
+
|
572
|
+
body:0:""
|
573
|
+
p:0:""
|
574
|
+
text:0:""
|
575
|
+
text:1:"X"
|
576
|
+
text:2:TRAILING_SPACE_IN_BLOCK
|
577
|
+
text:3:COLLAPSED_SPACE
|
578
|
+
p:1:""
|
579
|
+
body:1:"\n"
|
580
|
+
p:0:""
|
581
|
+
text:0:""
|
582
|
+
text:1:"Y"
|
583
|
+
|
584
|
+
A character is a TRAILING_SPACE_IN_BLOCK iff:
|
585
|
+
|
586
|
+
- There is no uncollapsed character after it within the visible containing block element
|
587
|
+
|
588
|
+
A character is a TRAILING_SPACE_BEFORE_BR iff:
|
589
|
+
|
590
|
+
- There is no uncollapsed character after it preceding a <br> element
|
591
|
+
|
592
|
+
An element has inner text iff
|
593
|
+
|
594
|
+
- It is not hidden
|
595
|
+
- It contains an uncollapsed character
|
596
|
+
|
597
|
+
All trailing spaces (pre-line, before <br>, end of block) require definite non-empty characters to render.
|
598
|
+
*/
|
599
|
+
|
600
|
+
while (pos !== endPos) {
|
601
|
+
pos.prepopulateChar();
|
602
|
+
if (pos.isDefinitelyNonEmpty()) {
|
603
|
+
return true;
|
604
|
+
}
|
605
|
+
pos = backward ? pos.previousVisible() : pos.nextVisible();
|
606
|
+
}
|
607
|
+
|
608
|
+
return false;
|
609
|
+
}, "node"),
|
610
|
+
|
611
|
+
isRenderedBlock: createCachingGetter("isRenderedBlock", function(el) {
|
612
|
+
// Ensure that a block element containing a <br> is considered to have inner text
|
613
|
+
var brs = el.getElementsByTagName("br");
|
614
|
+
for (var i = 0, len = brs.length; i < len; ++i) {
|
615
|
+
if (!isCollapsedNode(brs[i])) {
|
616
|
+
return true;
|
617
|
+
}
|
618
|
+
}
|
619
|
+
return this.hasInnerText();
|
620
|
+
}, "node"),
|
621
|
+
|
622
|
+
getTrailingSpace: createCachingGetter("trailingSpace", function(el) {
|
623
|
+
if (el.tagName.toLowerCase() == "br") {
|
624
|
+
return "";
|
625
|
+
} else {
|
626
|
+
switch (this.getComputedDisplay()) {
|
627
|
+
case "inline":
|
628
|
+
var child = el.lastChild;
|
629
|
+
while (child) {
|
630
|
+
if (!isIgnoredNode(child)) {
|
631
|
+
return (child.nodeType == 1) ? this.session.getNodeWrapper(child).getTrailingSpace() : "";
|
632
|
+
}
|
633
|
+
child = child.previousSibling;
|
634
|
+
}
|
635
|
+
break;
|
636
|
+
case "inline-block":
|
637
|
+
case "inline-table":
|
638
|
+
case "none":
|
639
|
+
case "table-column":
|
640
|
+
case "table-column-group":
|
641
|
+
break;
|
642
|
+
case "table-cell":
|
643
|
+
return "\t";
|
644
|
+
default:
|
645
|
+
return this.isRenderedBlock(true) ? "\n" : "";
|
646
|
+
}
|
647
|
+
}
|
648
|
+
return "";
|
649
|
+
}, "node"),
|
650
|
+
|
651
|
+
getLeadingSpace: createCachingGetter("leadingSpace", function(el) {
|
652
|
+
switch (this.getComputedDisplay()) {
|
653
|
+
case "inline":
|
654
|
+
case "inline-block":
|
655
|
+
case "inline-table":
|
656
|
+
case "none":
|
657
|
+
case "table-column":
|
658
|
+
case "table-column-group":
|
659
|
+
case "table-cell":
|
660
|
+
break;
|
661
|
+
default:
|
662
|
+
return this.isRenderedBlock(false) ? "\n" : "";
|
663
|
+
}
|
664
|
+
return "";
|
665
|
+
}, "node")
|
666
|
+
});
|
667
|
+
|
668
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
669
|
+
|
670
|
+
|
671
|
+
function Position(nodeWrapper, offset) {
|
672
|
+
this.offset = offset;
|
673
|
+
this.nodeWrapper = nodeWrapper;
|
674
|
+
this.node = nodeWrapper.node;
|
675
|
+
this.session = nodeWrapper.session;
|
676
|
+
this.cache = new Cache();
|
677
|
+
}
|
678
|
+
|
679
|
+
function inspectPosition() {
|
680
|
+
return "[Position(" + dom.inspectNode(this.node) + ":" + this.offset + ")]";
|
681
|
+
}
|
682
|
+
|
683
|
+
var positionProto = {
|
684
|
+
character: "",
|
685
|
+
characterType: EMPTY,
|
686
|
+
isBr: false,
|
687
|
+
|
688
|
+
/*
|
689
|
+
This method:
|
690
|
+
- Fully populates positions that have characters that can be determined independently of any other characters.
|
691
|
+
- Populates most types of space positions with a provisional character. The character is finalized later.
|
692
|
+
*/
|
693
|
+
prepopulateChar: function() {
|
694
|
+
var pos = this;
|
695
|
+
if (!pos.prepopulatedChar) {
|
696
|
+
var node = pos.node, offset = pos.offset;
|
697
|
+
var visibleChar = "", charType = EMPTY;
|
698
|
+
var finalizedChar = false;
|
699
|
+
if (offset > 0) {
|
700
|
+
if (node.nodeType == 3) {
|
701
|
+
var text = node.data;
|
702
|
+
var textChar = text.charAt(offset - 1);
|
703
|
+
|
704
|
+
var nodeInfo = pos.nodeWrapper.getTextNodeInfo();
|
705
|
+
var spaceRegex = nodeInfo.spaceRegex;
|
706
|
+
if (nodeInfo.collapseSpaces) {
|
707
|
+
if (spaceRegex.test(textChar)) {
|
708
|
+
// "If the character at position is from set, append a single space (U+0020) to newdata and advance
|
709
|
+
// position until the character at position is not from set."
|
710
|
+
|
711
|
+
// We also need to check for the case where we're in a pre-line and we have a space preceding a
|
712
|
+
// line break, because such spaces are collapsed in some browsers
|
713
|
+
if (offset > 1 && spaceRegex.test(text.charAt(offset - 2))) {
|
714
|
+
} else if (nodeInfo.preLine && text.charAt(offset) === "\n") {
|
715
|
+
visibleChar = " ";
|
716
|
+
charType = PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK;
|
717
|
+
} else {
|
718
|
+
visibleChar = " ";
|
719
|
+
//pos.checkForFollowingLineBreak = true;
|
720
|
+
charType = COLLAPSIBLE_SPACE;
|
721
|
+
}
|
722
|
+
} else {
|
723
|
+
visibleChar = textChar;
|
724
|
+
charType = NON_SPACE;
|
725
|
+
finalizedChar = true;
|
726
|
+
}
|
727
|
+
} else {
|
728
|
+
visibleChar = textChar;
|
729
|
+
charType = UNCOLLAPSIBLE_SPACE;
|
730
|
+
finalizedChar = true;
|
731
|
+
}
|
732
|
+
} else {
|
733
|
+
var nodePassed = node.childNodes[offset - 1];
|
734
|
+
if (nodePassed && nodePassed.nodeType == 1 && !isCollapsedNode(nodePassed)) {
|
735
|
+
if (nodePassed.tagName.toLowerCase() == "br") {
|
736
|
+
visibleChar = "\n";
|
737
|
+
pos.isBr = true;
|
738
|
+
charType = COLLAPSIBLE_SPACE;
|
739
|
+
finalizedChar = false;
|
740
|
+
} else {
|
741
|
+
pos.checkForTrailingSpace = true;
|
742
|
+
}
|
743
|
+
}
|
744
|
+
|
745
|
+
// Check the leading space of the next node for the case when a block element follows an inline
|
746
|
+
// element or text node. In that case, there is an implied line break between the two nodes.
|
747
|
+
if (!visibleChar) {
|
748
|
+
var nextNode = node.childNodes[offset];
|
749
|
+
if (nextNode && nextNode.nodeType == 1 && !isCollapsedNode(nextNode)) {
|
750
|
+
pos.checkForLeadingSpace = true;
|
751
|
+
}
|
752
|
+
}
|
753
|
+
}
|
754
|
+
}
|
755
|
+
|
756
|
+
pos.prepopulatedChar = true;
|
757
|
+
pos.character = visibleChar;
|
758
|
+
pos.characterType = charType;
|
759
|
+
pos.isCharInvariant = finalizedChar;
|
760
|
+
}
|
761
|
+
},
|
762
|
+
|
763
|
+
isDefinitelyNonEmpty: function() {
|
764
|
+
var charType = this.characterType;
|
765
|
+
return charType == NON_SPACE || charType == UNCOLLAPSIBLE_SPACE;
|
766
|
+
},
|
767
|
+
|
768
|
+
// Resolve leading and trailing spaces, which may involve prepopulating other positions
|
769
|
+
resolveLeadingAndTrailingSpaces: function() {
|
770
|
+
if (!this.prepopulatedChar) {
|
771
|
+
this.prepopulateChar();
|
772
|
+
}
|
773
|
+
if (this.checkForTrailingSpace) {
|
774
|
+
var trailingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset - 1]).getTrailingSpace();
|
775
|
+
if (trailingSpace) {
|
776
|
+
this.isTrailingSpace = true;
|
777
|
+
this.character = trailingSpace;
|
778
|
+
this.characterType = COLLAPSIBLE_SPACE;
|
779
|
+
}
|
780
|
+
this.checkForTrailingSpace = false;
|
781
|
+
}
|
782
|
+
if (this.checkForLeadingSpace) {
|
783
|
+
var leadingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset]).getLeadingSpace();
|
784
|
+
if (leadingSpace) {
|
785
|
+
this.isLeadingSpace = true;
|
786
|
+
this.character = leadingSpace;
|
787
|
+
this.characterType = COLLAPSIBLE_SPACE;
|
788
|
+
}
|
789
|
+
this.checkForLeadingSpace = false;
|
790
|
+
}
|
791
|
+
},
|
792
|
+
|
793
|
+
getPrecedingUncollapsedPosition: function(characterOptions) {
|
794
|
+
var pos = this, character;
|
795
|
+
while ( (pos = pos.previousVisible()) ) {
|
796
|
+
character = pos.getCharacter(characterOptions);
|
797
|
+
if (character !== "") {
|
798
|
+
return pos;
|
799
|
+
}
|
800
|
+
}
|
801
|
+
|
802
|
+
return null;
|
803
|
+
},
|
804
|
+
|
805
|
+
getCharacter: function(characterOptions) {
|
806
|
+
this.resolveLeadingAndTrailingSpaces();
|
807
|
+
|
808
|
+
// Check if this position's character is invariant (i.e. not dependent on character options) and return it
|
809
|
+
// if so
|
810
|
+
if (this.isCharInvariant) {
|
811
|
+
return this.character;
|
812
|
+
}
|
813
|
+
|
814
|
+
var cacheKey = ["character", characterOptions.includeSpaceBeforeBr, characterOptions.includeBlockContentTrailingSpace, characterOptions.includePreLineTrailingSpace].join("_");
|
815
|
+
var cachedChar = this.cache.get(cacheKey);
|
816
|
+
if (cachedChar !== null) {
|
817
|
+
return cachedChar;
|
818
|
+
}
|
819
|
+
|
820
|
+
// We need to actually get the character
|
821
|
+
var character = "";
|
822
|
+
var collapsible = (this.characterType == COLLAPSIBLE_SPACE);
|
823
|
+
|
824
|
+
var nextPos, previousPos/* = this.getPrecedingUncollapsedPosition(characterOptions)*/;
|
825
|
+
var gotPreviousPos = false;
|
826
|
+
var pos = this;
|
827
|
+
|
828
|
+
function getPreviousPos() {
|
829
|
+
if (!gotPreviousPos) {
|
830
|
+
previousPos = pos.getPrecedingUncollapsedPosition(characterOptions);
|
831
|
+
gotPreviousPos = true;
|
832
|
+
}
|
833
|
+
return previousPos;
|
834
|
+
}
|
835
|
+
|
836
|
+
// Disallow a collapsible space that is followed by a line break or is the last character
|
837
|
+
if (collapsible) {
|
838
|
+
// Disallow a collapsible space that follows a trailing space or line break, or is the first character
|
839
|
+
if (this.character == " " &&
|
840
|
+
(!getPreviousPos() || previousPos.isTrailingSpace || previousPos.character == "\n")) {
|
841
|
+
}
|
842
|
+
// Allow a leading line break unless it follows a line break
|
843
|
+
else if (this.character == "\n" && this.isLeadingSpace) {
|
844
|
+
if (getPreviousPos() && previousPos.character != "\n") {
|
845
|
+
character = "\n";
|
846
|
+
} else {
|
847
|
+
}
|
848
|
+
} else {
|
849
|
+
nextPos = this.nextUncollapsed();
|
850
|
+
if (nextPos) {
|
851
|
+
if (nextPos.isBr) {
|
852
|
+
this.type = TRAILING_SPACE_BEFORE_BR;
|
853
|
+
} else if (nextPos.isTrailingSpace && nextPos.character == "\n") {
|
854
|
+
this.type = TRAILING_SPACE_IN_BLOCK;
|
855
|
+
} else if (nextPos.isLeadingSpace && nextPos.character == "\n") {
|
856
|
+
this.type = TRAILING_SPACE_BEFORE_BLOCK;
|
857
|
+
}
|
858
|
+
|
859
|
+
if (nextPos.character === "\n") {
|
860
|
+
if (this.type == TRAILING_SPACE_BEFORE_BR && !characterOptions.includeSpaceBeforeBr) {
|
861
|
+
} else if (this.type == TRAILING_SPACE_BEFORE_BLOCK && !characterOptions.includeSpaceBeforeBlock) {
|
862
|
+
} else if (this.type == TRAILING_SPACE_IN_BLOCK && nextPos.isTrailingSpace && !characterOptions.includeBlockContentTrailingSpace) {
|
863
|
+
} else if (this.type == PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK && nextPos.type == NON_SPACE && !characterOptions.includePreLineTrailingSpace) {
|
864
|
+
} else if (this.character === "\n") {
|
865
|
+
if (nextPos.isTrailingSpace) {
|
866
|
+
if (this.isTrailingSpace) {
|
867
|
+
} else if (this.isBr) {
|
868
|
+
nextPos.type = TRAILING_LINE_BREAK_AFTER_BR;
|
869
|
+
|
870
|
+
if (getPreviousPos() && previousPos.isLeadingSpace && previousPos.character == "\n") {
|
871
|
+
nextPos.character = "";
|
872
|
+
} else {
|
873
|
+
//character = "\n";
|
874
|
+
//nextPos
|
875
|
+
/*
|
876
|
+
nextPos.character = "";
|
877
|
+
character = "\n";
|
878
|
+
*/
|
879
|
+
}
|
880
|
+
}
|
881
|
+
} else {
|
882
|
+
character = "\n";
|
883
|
+
}
|
884
|
+
} else if (this.character === " ") {
|
885
|
+
character = " ";
|
886
|
+
} else {
|
887
|
+
}
|
888
|
+
} else {
|
889
|
+
character = this.character;
|
890
|
+
}
|
891
|
+
} else {
|
892
|
+
}
|
893
|
+
}
|
894
|
+
}
|
895
|
+
|
896
|
+
// Collapse a br element that is followed by a trailing space
|
897
|
+
else if (this.character === "\n" &&
|
898
|
+
(!(nextPos = this.nextUncollapsed()) || nextPos.isTrailingSpace)) {
|
899
|
+
}
|
900
|
+
|
901
|
+
|
902
|
+
this.cache.set(cacheKey, character);
|
903
|
+
|
904
|
+
return character;
|
905
|
+
},
|
906
|
+
|
907
|
+
equals: function(pos) {
|
908
|
+
return !!pos && this.node === pos.node && this.offset === pos.offset;
|
909
|
+
},
|
910
|
+
|
911
|
+
inspect: inspectPosition,
|
912
|
+
|
913
|
+
toString: function() {
|
914
|
+
return this.character;
|
915
|
+
}
|
916
|
+
};
|
917
|
+
|
918
|
+
Position.prototype = positionProto;
|
919
|
+
|
920
|
+
extend(positionProto, {
|
921
|
+
next: createCachingGetter("nextPos", function(pos) {
|
922
|
+
var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session;
|
923
|
+
if (!node) {
|
924
|
+
return null;
|
925
|
+
}
|
926
|
+
var nextNode, nextOffset, child;
|
927
|
+
if (offset == nodeWrapper.getLength()) {
|
928
|
+
// Move onto the next node
|
929
|
+
nextNode = node.parentNode;
|
930
|
+
nextOffset = nextNode ? nodeWrapper.getNodeIndex() + 1 : 0;
|
931
|
+
} else {
|
932
|
+
if (nodeWrapper.isCharacterDataNode()) {
|
933
|
+
nextNode = node;
|
934
|
+
nextOffset = offset + 1;
|
935
|
+
} else {
|
936
|
+
child = node.childNodes[offset];
|
937
|
+
// Go into the children next, if children there are
|
938
|
+
if (session.getNodeWrapper(child).containsPositions()) {
|
939
|
+
nextNode = child;
|
940
|
+
nextOffset = 0;
|
941
|
+
} else {
|
942
|
+
nextNode = node;
|
943
|
+
nextOffset = offset + 1;
|
944
|
+
}
|
945
|
+
}
|
946
|
+
}
|
947
|
+
|
948
|
+
return nextNode ? session.getPosition(nextNode, nextOffset) : null;
|
949
|
+
}),
|
950
|
+
|
951
|
+
previous: createCachingGetter("previous", function(pos) {
|
952
|
+
var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session;
|
953
|
+
var previousNode, previousOffset, child;
|
954
|
+
if (offset == 0) {
|
955
|
+
previousNode = node.parentNode;
|
956
|
+
previousOffset = previousNode ? nodeWrapper.getNodeIndex() : 0;
|
957
|
+
} else {
|
958
|
+
if (nodeWrapper.isCharacterDataNode()) {
|
959
|
+
previousNode = node;
|
960
|
+
previousOffset = offset - 1;
|
961
|
+
} else {
|
962
|
+
child = node.childNodes[offset - 1];
|
963
|
+
// Go into the children next, if children there are
|
964
|
+
if (session.getNodeWrapper(child).containsPositions()) {
|
965
|
+
previousNode = child;
|
966
|
+
previousOffset = dom.getNodeLength(child);
|
967
|
+
} else {
|
968
|
+
previousNode = node;
|
969
|
+
previousOffset = offset - 1;
|
970
|
+
}
|
971
|
+
}
|
972
|
+
}
|
973
|
+
return previousNode ? session.getPosition(previousNode, previousOffset) : null;
|
974
|
+
}),
|
975
|
+
|
976
|
+
/*
|
977
|
+
Next and previous position moving functions that filter out
|
978
|
+
|
979
|
+
- Hidden (CSS visibility/display) elements
|
980
|
+
- Script and style elements
|
981
|
+
*/
|
982
|
+
nextVisible: createCachingGetter("nextVisible", function(pos) {
|
983
|
+
var next = pos.next();
|
984
|
+
if (!next) {
|
985
|
+
return null;
|
986
|
+
}
|
987
|
+
var nodeWrapper = next.nodeWrapper, node = next.node;
|
988
|
+
var newPos = next;
|
989
|
+
if (nodeWrapper.isCollapsed()) {
|
990
|
+
// We're skipping this node and all its descendants
|
991
|
+
newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex() + 1);
|
992
|
+
}
|
993
|
+
return newPos;
|
994
|
+
}),
|
995
|
+
|
996
|
+
nextUncollapsed: createCachingGetter("nextUncollapsed", function(pos) {
|
997
|
+
var nextPos = pos;
|
998
|
+
while ( (nextPos = nextPos.nextVisible()) ) {
|
999
|
+
nextPos.resolveLeadingAndTrailingSpaces();
|
1000
|
+
if (nextPos.character !== "") {
|
1001
|
+
return nextPos;
|
1002
|
+
}
|
1003
|
+
}
|
1004
|
+
return null;
|
1005
|
+
}),
|
1006
|
+
|
1007
|
+
previousVisible: createCachingGetter("previousVisible", function(pos) {
|
1008
|
+
var previous = pos.previous();
|
1009
|
+
if (!previous) {
|
1010
|
+
return null;
|
1011
|
+
}
|
1012
|
+
var nodeWrapper = previous.nodeWrapper, node = previous.node;
|
1013
|
+
var newPos = previous;
|
1014
|
+
if (nodeWrapper.isCollapsed()) {
|
1015
|
+
// We're skipping this node and all its descendants
|
1016
|
+
newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex());
|
1017
|
+
}
|
1018
|
+
return newPos;
|
1019
|
+
})
|
1020
|
+
});
|
1021
|
+
|
1022
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
1023
|
+
|
1024
|
+
var currentSession = null;
|
1025
|
+
|
1026
|
+
var Session = (function() {
|
1027
|
+
function createWrapperCache(nodeProperty) {
|
1028
|
+
var cache = new Cache();
|
1029
|
+
|
1030
|
+
return {
|
1031
|
+
get: function(node) {
|
1032
|
+
var wrappersByProperty = cache.get(node[nodeProperty]);
|
1033
|
+
if (wrappersByProperty) {
|
1034
|
+
for (var i = 0, wrapper; wrapper = wrappersByProperty[i++]; ) {
|
1035
|
+
if (wrapper.node === node) {
|
1036
|
+
return wrapper;
|
1037
|
+
}
|
1038
|
+
}
|
1039
|
+
}
|
1040
|
+
return null;
|
1041
|
+
},
|
1042
|
+
|
1043
|
+
set: function(nodeWrapper) {
|
1044
|
+
var property = nodeWrapper.node[nodeProperty];
|
1045
|
+
var wrappersByProperty = cache.get(property) || cache.set(property, []);
|
1046
|
+
wrappersByProperty.push(nodeWrapper);
|
1047
|
+
}
|
1048
|
+
};
|
1049
|
+
}
|
1050
|
+
|
1051
|
+
var uniqueIDSupported = util.isHostProperty(document.documentElement, "uniqueID");
|
1052
|
+
|
1053
|
+
function Session() {
|
1054
|
+
this.initCaches();
|
1055
|
+
}
|
1056
|
+
|
1057
|
+
Session.prototype = {
|
1058
|
+
initCaches: function() {
|
1059
|
+
this.elementCache = uniqueIDSupported ? (function() {
|
1060
|
+
var elementsCache = new Cache();
|
1061
|
+
|
1062
|
+
return {
|
1063
|
+
get: function(el) {
|
1064
|
+
return elementsCache.get(el.uniqueID);
|
1065
|
+
},
|
1066
|
+
|
1067
|
+
set: function(elWrapper) {
|
1068
|
+
elementsCache.set(elWrapper.node.uniqueID, elWrapper);
|
1069
|
+
}
|
1070
|
+
};
|
1071
|
+
})() : createWrapperCache("tagName");
|
1072
|
+
|
1073
|
+
// Store text nodes keyed by data, although we may need to truncate this
|
1074
|
+
this.textNodeCache = createWrapperCache("data");
|
1075
|
+
this.otherNodeCache = createWrapperCache("nodeName");
|
1076
|
+
},
|
1077
|
+
|
1078
|
+
getNodeWrapper: function(node) {
|
1079
|
+
var wrapperCache;
|
1080
|
+
switch (node.nodeType) {
|
1081
|
+
case 1:
|
1082
|
+
wrapperCache = this.elementCache;
|
1083
|
+
break;
|
1084
|
+
case 3:
|
1085
|
+
wrapperCache = this.textNodeCache;
|
1086
|
+
break;
|
1087
|
+
default:
|
1088
|
+
wrapperCache = this.otherNodeCache;
|
1089
|
+
break;
|
1090
|
+
}
|
1091
|
+
|
1092
|
+
var wrapper = wrapperCache.get(node);
|
1093
|
+
if (!wrapper) {
|
1094
|
+
wrapper = new NodeWrapper(node, this);
|
1095
|
+
wrapperCache.set(wrapper);
|
1096
|
+
}
|
1097
|
+
return wrapper;
|
1098
|
+
},
|
1099
|
+
|
1100
|
+
getPosition: function(node, offset) {
|
1101
|
+
return this.getNodeWrapper(node).getPosition(offset);
|
1102
|
+
},
|
1103
|
+
|
1104
|
+
getRangeBoundaryPosition: function(range, isStart) {
|
1105
|
+
var prefix = isStart ? "start" : "end";
|
1106
|
+
return this.getPosition(range[prefix + "Container"], range[prefix + "Offset"]);
|
1107
|
+
},
|
1108
|
+
|
1109
|
+
detach: function() {
|
1110
|
+
this.elementCache = this.textNodeCache = this.otherNodeCache = null;
|
1111
|
+
}
|
1112
|
+
};
|
1113
|
+
|
1114
|
+
return Session;
|
1115
|
+
})();
|
1116
|
+
|
1117
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
1118
|
+
|
1119
|
+
function startSession() {
|
1120
|
+
endSession();
|
1121
|
+
return (currentSession = new Session());
|
1122
|
+
}
|
1123
|
+
|
1124
|
+
function getSession() {
|
1125
|
+
return currentSession || startSession();
|
1126
|
+
}
|
1127
|
+
|
1128
|
+
function endSession() {
|
1129
|
+
if (currentSession) {
|
1130
|
+
currentSession.detach();
|
1131
|
+
}
|
1132
|
+
currentSession = null;
|
1133
|
+
}
|
1134
|
+
|
1135
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
1136
|
+
|
1137
|
+
// Extensions to the rangy.dom utility object
|
1138
|
+
|
1139
|
+
extend(dom, {
|
1140
|
+
nextNode: nextNode,
|
1141
|
+
previousNode: previousNode
|
1142
|
+
});
|
1143
|
+
|
1144
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
1145
|
+
|
1146
|
+
function createCharacterIterator(startPos, backward, endPos, characterOptions) {
|
1147
|
+
|
1148
|
+
// Adjust the end position to ensure that it is actually reached
|
1149
|
+
if (endPos) {
|
1150
|
+
if (backward) {
|
1151
|
+
if (isCollapsedNode(endPos.node)) {
|
1152
|
+
endPos = startPos.previousVisible();
|
1153
|
+
}
|
1154
|
+
} else {
|
1155
|
+
if (isCollapsedNode(endPos.node)) {
|
1156
|
+
endPos = endPos.nextVisible();
|
1157
|
+
}
|
1158
|
+
}
|
1159
|
+
}
|
1160
|
+
|
1161
|
+
var pos = startPos, finished = false;
|
1162
|
+
|
1163
|
+
function next() {
|
1164
|
+
var newPos = null, charPos = null;
|
1165
|
+
if (backward) {
|
1166
|
+
charPos = pos;
|
1167
|
+
if (!finished) {
|
1168
|
+
pos = pos.previousVisible();
|
1169
|
+
finished = !pos || (endPos && pos.equals(endPos));
|
1170
|
+
}
|
1171
|
+
} else {
|
1172
|
+
if (!finished) {
|
1173
|
+
charPos = pos = pos.nextVisible();
|
1174
|
+
finished = !pos || (endPos && pos.equals(endPos));
|
1175
|
+
}
|
1176
|
+
}
|
1177
|
+
if (finished) {
|
1178
|
+
pos = null;
|
1179
|
+
}
|
1180
|
+
return charPos;
|
1181
|
+
}
|
1182
|
+
|
1183
|
+
var previousTextPos, returnPreviousTextPos = false;
|
1184
|
+
|
1185
|
+
return {
|
1186
|
+
next: function() {
|
1187
|
+
if (returnPreviousTextPos) {
|
1188
|
+
returnPreviousTextPos = false;
|
1189
|
+
return previousTextPos;
|
1190
|
+
} else {
|
1191
|
+
var pos, character;
|
1192
|
+
while ( (pos = next()) ) {
|
1193
|
+
character = pos.getCharacter(characterOptions);
|
1194
|
+
if (character) {
|
1195
|
+
previousTextPos = pos;
|
1196
|
+
return pos;
|
1197
|
+
}
|
1198
|
+
}
|
1199
|
+
return null;
|
1200
|
+
}
|
1201
|
+
},
|
1202
|
+
|
1203
|
+
rewind: function() {
|
1204
|
+
if (previousTextPos) {
|
1205
|
+
returnPreviousTextPos = true;
|
1206
|
+
} else {
|
1207
|
+
throw module.createError("createCharacterIterator: cannot rewind. Only one position can be rewound.");
|
1208
|
+
}
|
1209
|
+
},
|
1210
|
+
|
1211
|
+
dispose: function() {
|
1212
|
+
startPos = endPos = null;
|
1213
|
+
}
|
1214
|
+
};
|
1215
|
+
}
|
1216
|
+
|
1217
|
+
var arrayIndexOf = Array.prototype.indexOf ?
|
1218
|
+
function(arr, val) {
|
1219
|
+
return arr.indexOf(val);
|
1220
|
+
} :
|
1221
|
+
function(arr, val) {
|
1222
|
+
for (var i = 0, len = arr.length; i < len; ++i) {
|
1223
|
+
if (arr[i] === val) {
|
1224
|
+
return i;
|
1225
|
+
}
|
1226
|
+
}
|
1227
|
+
return -1;
|
1228
|
+
};
|
1229
|
+
|
1230
|
+
// Provides a pair of iterators over text positions, tokenized. Transparently requests more text when next()
|
1231
|
+
// is called and there is no more tokenized text
|
1232
|
+
function createTokenizedTextProvider(pos, characterOptions, wordOptions) {
|
1233
|
+
var forwardIterator = createCharacterIterator(pos, false, null, characterOptions);
|
1234
|
+
var backwardIterator = createCharacterIterator(pos, true, null, characterOptions);
|
1235
|
+
var tokenizer = wordOptions.tokenizer;
|
1236
|
+
|
1237
|
+
// Consumes a word and the whitespace beyond it
|
1238
|
+
function consumeWord(forward) {
|
1239
|
+
var pos, textChar;
|
1240
|
+
var newChars = [], it = forward ? forwardIterator : backwardIterator;
|
1241
|
+
|
1242
|
+
var passedWordBoundary = false, insideWord = false;
|
1243
|
+
|
1244
|
+
while ( (pos = it.next()) ) {
|
1245
|
+
textChar = pos.character;
|
1246
|
+
|
1247
|
+
|
1248
|
+
if (allWhiteSpaceRegex.test(textChar)) {
|
1249
|
+
if (insideWord) {
|
1250
|
+
insideWord = false;
|
1251
|
+
passedWordBoundary = true;
|
1252
|
+
}
|
1253
|
+
} else {
|
1254
|
+
if (passedWordBoundary) {
|
1255
|
+
it.rewind();
|
1256
|
+
break;
|
1257
|
+
} else {
|
1258
|
+
insideWord = true;
|
1259
|
+
}
|
1260
|
+
}
|
1261
|
+
newChars.push(pos);
|
1262
|
+
}
|
1263
|
+
|
1264
|
+
|
1265
|
+
return newChars;
|
1266
|
+
}
|
1267
|
+
|
1268
|
+
// Get initial word surrounding initial position and tokenize it
|
1269
|
+
var forwardChars = consumeWord(true);
|
1270
|
+
var backwardChars = consumeWord(false).reverse();
|
1271
|
+
var tokens = tokenizer(backwardChars.concat(forwardChars), wordOptions);
|
1272
|
+
|
1273
|
+
// Create initial token buffers
|
1274
|
+
var forwardTokensBuffer = forwardChars.length ?
|
1275
|
+
tokens.slice(arrayIndexOf(tokens, forwardChars[0].token)) : [];
|
1276
|
+
|
1277
|
+
var backwardTokensBuffer = backwardChars.length ?
|
1278
|
+
tokens.slice(0, arrayIndexOf(tokens, backwardChars.pop().token) + 1) : [];
|
1279
|
+
|
1280
|
+
function inspectBuffer(buffer) {
|
1281
|
+
var textPositions = ["[" + buffer.length + "]"];
|
1282
|
+
for (var i = 0; i < buffer.length; ++i) {
|
1283
|
+
textPositions.push("(word: " + buffer[i] + ", is word: " + buffer[i].isWord + ")");
|
1284
|
+
}
|
1285
|
+
return textPositions;
|
1286
|
+
}
|
1287
|
+
|
1288
|
+
|
1289
|
+
return {
|
1290
|
+
nextEndToken: function() {
|
1291
|
+
var lastToken, forwardChars;
|
1292
|
+
|
1293
|
+
// If we're down to the last token, consume character chunks until we have a word or run out of
|
1294
|
+
// characters to consume
|
1295
|
+
while ( forwardTokensBuffer.length == 1 &&
|
1296
|
+
!(lastToken = forwardTokensBuffer[0]).isWord &&
|
1297
|
+
(forwardChars = consumeWord(true)).length > 0) {
|
1298
|
+
|
1299
|
+
// Merge trailing non-word into next word and tokenize
|
1300
|
+
forwardTokensBuffer = tokenizer(lastToken.chars.concat(forwardChars), wordOptions);
|
1301
|
+
}
|
1302
|
+
|
1303
|
+
return forwardTokensBuffer.shift();
|
1304
|
+
},
|
1305
|
+
|
1306
|
+
previousStartToken: function() {
|
1307
|
+
var lastToken, backwardChars;
|
1308
|
+
|
1309
|
+
// If we're down to the last token, consume character chunks until we have a word or run out of
|
1310
|
+
// characters to consume
|
1311
|
+
while ( backwardTokensBuffer.length == 1 &&
|
1312
|
+
!(lastToken = backwardTokensBuffer[0]).isWord &&
|
1313
|
+
(backwardChars = consumeWord(false)).length > 0) {
|
1314
|
+
|
1315
|
+
// Merge leading non-word into next word and tokenize
|
1316
|
+
backwardTokensBuffer = tokenizer(backwardChars.reverse().concat(lastToken.chars), wordOptions);
|
1317
|
+
}
|
1318
|
+
|
1319
|
+
return backwardTokensBuffer.pop();
|
1320
|
+
},
|
1321
|
+
|
1322
|
+
dispose: function() {
|
1323
|
+
forwardIterator.dispose();
|
1324
|
+
backwardIterator.dispose();
|
1325
|
+
forwardTokensBuffer = backwardTokensBuffer = null;
|
1326
|
+
}
|
1327
|
+
};
|
1328
|
+
}
|
1329
|
+
|
1330
|
+
function movePositionBy(pos, unit, count, characterOptions, wordOptions) {
|
1331
|
+
var unitsMoved = 0, currentPos, newPos = pos, charIterator, nextPos, absCount = Math.abs(count), token;
|
1332
|
+
if (count !== 0) {
|
1333
|
+
var backward = (count < 0);
|
1334
|
+
|
1335
|
+
switch (unit) {
|
1336
|
+
case CHARACTER:
|
1337
|
+
charIterator = createCharacterIterator(pos, backward, null, characterOptions);
|
1338
|
+
while ( (currentPos = charIterator.next()) && unitsMoved < absCount ) {
|
1339
|
+
++unitsMoved;
|
1340
|
+
newPos = currentPos;
|
1341
|
+
}
|
1342
|
+
nextPos = currentPos;
|
1343
|
+
charIterator.dispose();
|
1344
|
+
break;
|
1345
|
+
case WORD:
|
1346
|
+
var tokenizedTextProvider = createTokenizedTextProvider(pos, characterOptions, wordOptions);
|
1347
|
+
var next = backward ? tokenizedTextProvider.previousStartToken : tokenizedTextProvider.nextEndToken;
|
1348
|
+
|
1349
|
+
while ( (token = next()) && unitsMoved < absCount ) {
|
1350
|
+
if (token.isWord) {
|
1351
|
+
++unitsMoved;
|
1352
|
+
newPos = backward ? token.chars[0] : token.chars[token.chars.length - 1];
|
1353
|
+
}
|
1354
|
+
}
|
1355
|
+
break;
|
1356
|
+
default:
|
1357
|
+
throw new Error("movePositionBy: unit '" + unit + "' not implemented");
|
1358
|
+
}
|
1359
|
+
|
1360
|
+
// Perform any necessary position tweaks
|
1361
|
+
if (backward) {
|
1362
|
+
newPos = newPos.previousVisible();
|
1363
|
+
unitsMoved = -unitsMoved;
|
1364
|
+
} else if (newPos && newPos.isLeadingSpace) {
|
1365
|
+
// Tweak the position for the case of a leading space. The problem is that an uncollapsed leading space
|
1366
|
+
// before a block element (for example, the line break between "1" and "2" in the following HTML:
|
1367
|
+
// "1<p>2</p>") is considered to be attached to the position immediately before the block element, which
|
1368
|
+
// corresponds with a different selection position in most browsers from the one we want (i.e. at the
|
1369
|
+
// start of the contents of the block element). We get round this by advancing the position returned to
|
1370
|
+
// the last possible equivalent visible position.
|
1371
|
+
if (unit == WORD) {
|
1372
|
+
charIterator = createCharacterIterator(pos, false, null, characterOptions);
|
1373
|
+
nextPos = charIterator.next();
|
1374
|
+
charIterator.dispose();
|
1375
|
+
}
|
1376
|
+
if (nextPos) {
|
1377
|
+
newPos = nextPos.previousVisible();
|
1378
|
+
}
|
1379
|
+
}
|
1380
|
+
}
|
1381
|
+
|
1382
|
+
|
1383
|
+
return {
|
1384
|
+
position: newPos,
|
1385
|
+
unitsMoved: unitsMoved
|
1386
|
+
};
|
1387
|
+
}
|
1388
|
+
|
1389
|
+
function createRangeCharacterIterator(session, range, characterOptions, backward) {
|
1390
|
+
var rangeStart = session.getRangeBoundaryPosition(range, true);
|
1391
|
+
var rangeEnd = session.getRangeBoundaryPosition(range, false);
|
1392
|
+
var itStart = backward ? rangeEnd : rangeStart;
|
1393
|
+
var itEnd = backward ? rangeStart : rangeEnd;
|
1394
|
+
|
1395
|
+
return createCharacterIterator(itStart, !!backward, itEnd, characterOptions);
|
1396
|
+
}
|
1397
|
+
|
1398
|
+
function getRangeCharacters(session, range, characterOptions) {
|
1399
|
+
|
1400
|
+
var chars = [], it = createRangeCharacterIterator(session, range, characterOptions), pos;
|
1401
|
+
while ( (pos = it.next()) ) {
|
1402
|
+
chars.push(pos);
|
1403
|
+
}
|
1404
|
+
|
1405
|
+
it.dispose();
|
1406
|
+
return chars;
|
1407
|
+
}
|
1408
|
+
|
1409
|
+
function isWholeWord(startPos, endPos, wordOptions) {
|
1410
|
+
var range = api.createRange(startPos.node);
|
1411
|
+
range.setStartAndEnd(startPos.node, startPos.offset, endPos.node, endPos.offset);
|
1412
|
+
var returnVal = !range.expand("word", wordOptions);
|
1413
|
+
range.detach();
|
1414
|
+
return returnVal;
|
1415
|
+
}
|
1416
|
+
|
1417
|
+
function findTextFromPosition(initialPos, searchTerm, isRegex, searchScopeRange, findOptions) {
|
1418
|
+
var backward = isDirectionBackward(findOptions.direction);
|
1419
|
+
var it = createCharacterIterator(
|
1420
|
+
initialPos,
|
1421
|
+
backward,
|
1422
|
+
initialPos.session.getRangeBoundaryPosition(searchScopeRange, backward),
|
1423
|
+
findOptions
|
1424
|
+
);
|
1425
|
+
var text = "", chars = [], pos, currentChar, matchStartIndex, matchEndIndex;
|
1426
|
+
var result, insideRegexMatch;
|
1427
|
+
var returnValue = null;
|
1428
|
+
|
1429
|
+
function handleMatch(startIndex, endIndex) {
|
1430
|
+
var startPos = chars[startIndex].previousVisible();
|
1431
|
+
var endPos = chars[endIndex - 1];
|
1432
|
+
var valid = (!findOptions.wholeWordsOnly || isWholeWord(startPos, endPos, findOptions.wordOptions));
|
1433
|
+
|
1434
|
+
return {
|
1435
|
+
startPos: startPos,
|
1436
|
+
endPos: endPos,
|
1437
|
+
valid: valid
|
1438
|
+
};
|
1439
|
+
}
|
1440
|
+
|
1441
|
+
while ( (pos = it.next()) ) {
|
1442
|
+
currentChar = pos.character;
|
1443
|
+
if (!isRegex && !findOptions.caseSensitive) {
|
1444
|
+
currentChar = currentChar.toLowerCase();
|
1445
|
+
}
|
1446
|
+
|
1447
|
+
if (backward) {
|
1448
|
+
chars.unshift(pos);
|
1449
|
+
text = currentChar + text;
|
1450
|
+
} else {
|
1451
|
+
chars.push(pos);
|
1452
|
+
text += currentChar;
|
1453
|
+
}
|
1454
|
+
|
1455
|
+
//console.log("text " + text)
|
1456
|
+
|
1457
|
+
if (isRegex) {
|
1458
|
+
result = searchTerm.exec(text);
|
1459
|
+
if (result) {
|
1460
|
+
if (insideRegexMatch) {
|
1461
|
+
// Check whether the match is now over
|
1462
|
+
matchStartIndex = result.index;
|
1463
|
+
matchEndIndex = matchStartIndex + result[0].length;
|
1464
|
+
if ((!backward && matchEndIndex < text.length) || (backward && matchStartIndex > 0)) {
|
1465
|
+
returnValue = handleMatch(matchStartIndex, matchEndIndex);
|
1466
|
+
break;
|
1467
|
+
}
|
1468
|
+
} else {
|
1469
|
+
insideRegexMatch = true;
|
1470
|
+
}
|
1471
|
+
}
|
1472
|
+
} else if ( (matchStartIndex = text.indexOf(searchTerm)) != -1 ) {
|
1473
|
+
returnValue = handleMatch(matchStartIndex, matchStartIndex + searchTerm.length);
|
1474
|
+
break;
|
1475
|
+
}
|
1476
|
+
}
|
1477
|
+
|
1478
|
+
// Check whether regex match extends to the end of the range
|
1479
|
+
if (insideRegexMatch) {
|
1480
|
+
returnValue = handleMatch(matchStartIndex, matchEndIndex);
|
1481
|
+
}
|
1482
|
+
it.dispose();
|
1483
|
+
|
1484
|
+
return returnValue;
|
1485
|
+
}
|
1486
|
+
|
1487
|
+
function createEntryPointFunction(func) {
|
1488
|
+
return function() {
|
1489
|
+
var sessionRunning = !!currentSession;
|
1490
|
+
var session = getSession();
|
1491
|
+
var args = [session].concat( util.toArray(arguments) );
|
1492
|
+
var returnValue = func.apply(this, args);
|
1493
|
+
if (!sessionRunning) {
|
1494
|
+
endSession();
|
1495
|
+
}
|
1496
|
+
return returnValue;
|
1497
|
+
};
|
1498
|
+
}
|
1499
|
+
|
1500
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
1501
|
+
|
1502
|
+
// Extensions to the Rangy Range object
|
1503
|
+
|
1504
|
+
function createRangeBoundaryMover(isStart, collapse) {
|
1505
|
+
/*
|
1506
|
+
Unit can be "character" or "word"
|
1507
|
+
Options:
|
1508
|
+
|
1509
|
+
- includeTrailingSpace
|
1510
|
+
- wordRegex
|
1511
|
+
- tokenizer
|
1512
|
+
- collapseSpaceBeforeLineBreak
|
1513
|
+
*/
|
1514
|
+
return createEntryPointFunction(
|
1515
|
+
function(session, unit, count, moveOptions) {
|
1516
|
+
if (typeof count == "undefined") {
|
1517
|
+
count = unit;
|
1518
|
+
unit = CHARACTER;
|
1519
|
+
}
|
1520
|
+
moveOptions = createOptions(moveOptions, defaultMoveOptions);
|
1521
|
+
var characterOptions = createCharacterOptions(moveOptions.characterOptions);
|
1522
|
+
var wordOptions = createWordOptions(moveOptions.wordOptions);
|
1523
|
+
|
1524
|
+
var boundaryIsStart = isStart;
|
1525
|
+
if (collapse) {
|
1526
|
+
boundaryIsStart = (count >= 0);
|
1527
|
+
this.collapse(!boundaryIsStart);
|
1528
|
+
}
|
1529
|
+
var moveResult = movePositionBy(session.getRangeBoundaryPosition(this, boundaryIsStart), unit, count, characterOptions, wordOptions);
|
1530
|
+
var newPos = moveResult.position;
|
1531
|
+
this[boundaryIsStart ? "setStart" : "setEnd"](newPos.node, newPos.offset);
|
1532
|
+
return moveResult.unitsMoved;
|
1533
|
+
}
|
1534
|
+
);
|
1535
|
+
}
|
1536
|
+
|
1537
|
+
function createRangeTrimmer(isStart) {
|
1538
|
+
return createEntryPointFunction(
|
1539
|
+
function(session, characterOptions) {
|
1540
|
+
characterOptions = createCharacterOptions(characterOptions);
|
1541
|
+
var pos;
|
1542
|
+
var it = createRangeCharacterIterator(session, this, characterOptions, !isStart);
|
1543
|
+
var trimCharCount = 0;
|
1544
|
+
while ( (pos = it.next()) && allWhiteSpaceRegex.test(pos.character) ) {
|
1545
|
+
++trimCharCount;
|
1546
|
+
}
|
1547
|
+
it.dispose();
|
1548
|
+
var trimmed = (trimCharCount > 0);
|
1549
|
+
if (trimmed) {
|
1550
|
+
this[isStart ? "moveStart" : "moveEnd"](
|
1551
|
+
"character",
|
1552
|
+
isStart ? trimCharCount : -trimCharCount,
|
1553
|
+
{ characterOptions: characterOptions }
|
1554
|
+
);
|
1555
|
+
}
|
1556
|
+
return trimmed;
|
1557
|
+
}
|
1558
|
+
);
|
1559
|
+
}
|
1560
|
+
|
1561
|
+
extend(api.rangePrototype, {
|
1562
|
+
moveStart: createRangeBoundaryMover(true, false),
|
1563
|
+
|
1564
|
+
moveEnd: createRangeBoundaryMover(false, false),
|
1565
|
+
|
1566
|
+
move: createRangeBoundaryMover(true, true),
|
1567
|
+
|
1568
|
+
trimStart: createRangeTrimmer(true),
|
1569
|
+
|
1570
|
+
trimEnd: createRangeTrimmer(false),
|
1571
|
+
|
1572
|
+
trim: createEntryPointFunction(
|
1573
|
+
function(session, characterOptions) {
|
1574
|
+
var startTrimmed = this.trimStart(characterOptions), endTrimmed = this.trimEnd(characterOptions);
|
1575
|
+
return startTrimmed || endTrimmed;
|
1576
|
+
}
|
1577
|
+
),
|
1578
|
+
|
1579
|
+
expand: createEntryPointFunction(
|
1580
|
+
function(session, unit, expandOptions) {
|
1581
|
+
var moved = false;
|
1582
|
+
expandOptions = createOptions(expandOptions, defaultExpandOptions);
|
1583
|
+
var characterOptions = createCharacterOptions(expandOptions.characterOptions);
|
1584
|
+
if (!unit) {
|
1585
|
+
unit = CHARACTER;
|
1586
|
+
}
|
1587
|
+
if (unit == WORD) {
|
1588
|
+
var wordOptions = createWordOptions(expandOptions.wordOptions);
|
1589
|
+
var startPos = session.getRangeBoundaryPosition(this, true);
|
1590
|
+
var endPos = session.getRangeBoundaryPosition(this, false);
|
1591
|
+
|
1592
|
+
var startTokenizedTextProvider = createTokenizedTextProvider(startPos, characterOptions, wordOptions);
|
1593
|
+
var startToken = startTokenizedTextProvider.nextEndToken();
|
1594
|
+
var newStartPos = startToken.chars[0].previousVisible();
|
1595
|
+
var endToken, newEndPos;
|
1596
|
+
|
1597
|
+
if (this.collapsed) {
|
1598
|
+
endToken = startToken;
|
1599
|
+
} else {
|
1600
|
+
var endTokenizedTextProvider = createTokenizedTextProvider(endPos, characterOptions, wordOptions);
|
1601
|
+
endToken = endTokenizedTextProvider.previousStartToken();
|
1602
|
+
}
|
1603
|
+
newEndPos = endToken.chars[endToken.chars.length - 1];
|
1604
|
+
|
1605
|
+
if (!newStartPos.equals(startPos)) {
|
1606
|
+
this.setStart(newStartPos.node, newStartPos.offset);
|
1607
|
+
moved = true;
|
1608
|
+
}
|
1609
|
+
if (newEndPos && !newEndPos.equals(endPos)) {
|
1610
|
+
this.setEnd(newEndPos.node, newEndPos.offset);
|
1611
|
+
moved = true;
|
1612
|
+
}
|
1613
|
+
|
1614
|
+
if (expandOptions.trim) {
|
1615
|
+
if (expandOptions.trimStart) {
|
1616
|
+
moved = this.trimStart(characterOptions) || moved;
|
1617
|
+
}
|
1618
|
+
if (expandOptions.trimEnd) {
|
1619
|
+
moved = this.trimEnd(characterOptions) || moved;
|
1620
|
+
}
|
1621
|
+
}
|
1622
|
+
|
1623
|
+
return moved;
|
1624
|
+
} else {
|
1625
|
+
return this.moveEnd(CHARACTER, 1, expandOptions);
|
1626
|
+
}
|
1627
|
+
}
|
1628
|
+
),
|
1629
|
+
|
1630
|
+
text: createEntryPointFunction(
|
1631
|
+
function(session, characterOptions) {
|
1632
|
+
return this.collapsed ?
|
1633
|
+
"" : getRangeCharacters(session, this, createCharacterOptions(characterOptions)).join("");
|
1634
|
+
}
|
1635
|
+
),
|
1636
|
+
|
1637
|
+
selectCharacters: createEntryPointFunction(
|
1638
|
+
function(session, containerNode, startIndex, endIndex, characterOptions) {
|
1639
|
+
var moveOptions = { characterOptions: characterOptions };
|
1640
|
+
if (!containerNode) {
|
1641
|
+
containerNode = getBody( this.getDocument() );
|
1642
|
+
}
|
1643
|
+
this.selectNodeContents(containerNode);
|
1644
|
+
this.collapse(true);
|
1645
|
+
this.moveStart("character", startIndex, moveOptions);
|
1646
|
+
this.collapse(true);
|
1647
|
+
this.moveEnd("character", endIndex - startIndex, moveOptions);
|
1648
|
+
}
|
1649
|
+
),
|
1650
|
+
|
1651
|
+
// Character indexes are relative to the start of node
|
1652
|
+
toCharacterRange: createEntryPointFunction(
|
1653
|
+
function(session, containerNode, characterOptions) {
|
1654
|
+
if (!containerNode) {
|
1655
|
+
containerNode = getBody( this.getDocument() );
|
1656
|
+
}
|
1657
|
+
var parent = containerNode.parentNode, nodeIndex = dom.getNodeIndex(containerNode);
|
1658
|
+
var rangeStartsBeforeNode = (dom.comparePoints(this.startContainer, this.endContainer, parent, nodeIndex) == -1);
|
1659
|
+
var rangeBetween = this.cloneRange();
|
1660
|
+
var startIndex, endIndex;
|
1661
|
+
if (rangeStartsBeforeNode) {
|
1662
|
+
rangeBetween.setStartAndEnd(this.startContainer, this.startOffset, parent, nodeIndex);
|
1663
|
+
startIndex = -rangeBetween.text(characterOptions).length;
|
1664
|
+
} else {
|
1665
|
+
rangeBetween.setStartAndEnd(parent, nodeIndex, this.startContainer, this.startOffset);
|
1666
|
+
startIndex = rangeBetween.text(characterOptions).length;
|
1667
|
+
}
|
1668
|
+
endIndex = startIndex + this.text(characterOptions).length;
|
1669
|
+
|
1670
|
+
return {
|
1671
|
+
start: startIndex,
|
1672
|
+
end: endIndex
|
1673
|
+
};
|
1674
|
+
}
|
1675
|
+
),
|
1676
|
+
|
1677
|
+
findText: createEntryPointFunction(
|
1678
|
+
function(session, searchTermParam, findOptions) {
|
1679
|
+
// Set up options
|
1680
|
+
findOptions = createOptions(findOptions, defaultFindOptions);
|
1681
|
+
|
1682
|
+
// Create word options if we're matching whole words only
|
1683
|
+
if (findOptions.wholeWordsOnly) {
|
1684
|
+
findOptions.wordOptions = createWordOptions(findOptions.wordOptions);
|
1685
|
+
|
1686
|
+
// We don't ever want trailing spaces for search results
|
1687
|
+
findOptions.wordOptions.includeTrailingSpace = false;
|
1688
|
+
}
|
1689
|
+
|
1690
|
+
var backward = isDirectionBackward(findOptions.direction);
|
1691
|
+
|
1692
|
+
// Create a range representing the search scope if none was provided
|
1693
|
+
var searchScopeRange = findOptions.withinRange;
|
1694
|
+
if (!searchScopeRange) {
|
1695
|
+
searchScopeRange = api.createRange();
|
1696
|
+
searchScopeRange.selectNodeContents(this.getDocument());
|
1697
|
+
}
|
1698
|
+
|
1699
|
+
// Examine and prepare the search term
|
1700
|
+
var searchTerm = searchTermParam, isRegex = false;
|
1701
|
+
if (typeof searchTerm == "string") {
|
1702
|
+
if (!findOptions.caseSensitive) {
|
1703
|
+
searchTerm = searchTerm.toLowerCase();
|
1704
|
+
}
|
1705
|
+
} else {
|
1706
|
+
isRegex = true;
|
1707
|
+
}
|
1708
|
+
|
1709
|
+
var initialPos = session.getRangeBoundaryPosition(this, !backward);
|
1710
|
+
|
1711
|
+
// Adjust initial position if it lies outside the search scope
|
1712
|
+
var comparison = searchScopeRange.comparePoint(initialPos.node, initialPos.offset);
|
1713
|
+
|
1714
|
+
if (comparison === -1) {
|
1715
|
+
initialPos = session.getRangeBoundaryPosition(searchScopeRange, true);
|
1716
|
+
} else if (comparison === 1) {
|
1717
|
+
initialPos = session.getRangeBoundaryPosition(searchScopeRange, false);
|
1718
|
+
}
|
1719
|
+
|
1720
|
+
var pos = initialPos;
|
1721
|
+
var wrappedAround = false;
|
1722
|
+
|
1723
|
+
// Try to find a match and ignore invalid ones
|
1724
|
+
var findResult;
|
1725
|
+
while (true) {
|
1726
|
+
findResult = findTextFromPosition(pos, searchTerm, isRegex, searchScopeRange, findOptions);
|
1727
|
+
|
1728
|
+
if (findResult) {
|
1729
|
+
if (findResult.valid) {
|
1730
|
+
this.setStartAndEnd(findResult.startPos.node, findResult.startPos.offset, findResult.endPos.node, findResult.endPos.offset);
|
1731
|
+
return true;
|
1732
|
+
} else {
|
1733
|
+
// We've found a match that is not a whole word, so we carry on searching from the point immediately
|
1734
|
+
// after the match
|
1735
|
+
pos = backward ? findResult.startPos : findResult.endPos;
|
1736
|
+
}
|
1737
|
+
} else if (findOptions.wrap && !wrappedAround) {
|
1738
|
+
// No result found but we're wrapping around and limiting the scope to the unsearched part of the range
|
1739
|
+
searchScopeRange = searchScopeRange.cloneRange();
|
1740
|
+
pos = session.getRangeBoundaryPosition(searchScopeRange, !backward);
|
1741
|
+
searchScopeRange.setBoundary(initialPos.node, initialPos.offset, backward);
|
1742
|
+
wrappedAround = true;
|
1743
|
+
} else {
|
1744
|
+
// Nothing found and we can't wrap around, so we're done
|
1745
|
+
return false;
|
1746
|
+
}
|
1747
|
+
}
|
1748
|
+
}
|
1749
|
+
),
|
1750
|
+
|
1751
|
+
pasteHtml: function(html) {
|
1752
|
+
this.deleteContents();
|
1753
|
+
if (html) {
|
1754
|
+
var frag = this.createContextualFragment(html);
|
1755
|
+
var lastChild = frag.lastChild;
|
1756
|
+
this.insertNode(frag);
|
1757
|
+
this.collapseAfter(lastChild);
|
1758
|
+
}
|
1759
|
+
}
|
1760
|
+
});
|
1761
|
+
|
1762
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
1763
|
+
|
1764
|
+
// Extensions to the Rangy Selection object
|
1765
|
+
|
1766
|
+
function createSelectionTrimmer(methodName) {
|
1767
|
+
return createEntryPointFunction(
|
1768
|
+
function(session, characterOptions) {
|
1769
|
+
var trimmed = false;
|
1770
|
+
this.changeEachRange(function(range) {
|
1771
|
+
trimmed = range[methodName](characterOptions) || trimmed;
|
1772
|
+
});
|
1773
|
+
return trimmed;
|
1774
|
+
}
|
1775
|
+
);
|
1776
|
+
}
|
1777
|
+
|
1778
|
+
extend(api.selectionPrototype, {
|
1779
|
+
expand: createEntryPointFunction(
|
1780
|
+
function(session, unit, expandOptions) {
|
1781
|
+
this.changeEachRange(function(range) {
|
1782
|
+
range.expand(unit, expandOptions);
|
1783
|
+
});
|
1784
|
+
}
|
1785
|
+
),
|
1786
|
+
|
1787
|
+
move: createEntryPointFunction(
|
1788
|
+
function(session, unit, count, options) {
|
1789
|
+
var unitsMoved = 0;
|
1790
|
+
if (this.focusNode) {
|
1791
|
+
this.collapse(this.focusNode, this.focusOffset);
|
1792
|
+
var range = this.getRangeAt(0);
|
1793
|
+
if (!options) {
|
1794
|
+
options = {};
|
1795
|
+
}
|
1796
|
+
options.characterOptions = createCaretCharacterOptions(options.characterOptions);
|
1797
|
+
unitsMoved = range.move(unit, count, options);
|
1798
|
+
this.setSingleRange(range);
|
1799
|
+
}
|
1800
|
+
return unitsMoved;
|
1801
|
+
}
|
1802
|
+
),
|
1803
|
+
|
1804
|
+
trimStart: createSelectionTrimmer("trimStart"),
|
1805
|
+
trimEnd: createSelectionTrimmer("trimEnd"),
|
1806
|
+
trim: createSelectionTrimmer("trim"),
|
1807
|
+
|
1808
|
+
selectCharacters: createEntryPointFunction(
|
1809
|
+
function(session, containerNode, startIndex, endIndex, direction, characterOptions) {
|
1810
|
+
var range = api.createRange(containerNode);
|
1811
|
+
range.selectCharacters(containerNode, startIndex, endIndex, characterOptions);
|
1812
|
+
this.setSingleRange(range, direction);
|
1813
|
+
}
|
1814
|
+
),
|
1815
|
+
|
1816
|
+
saveCharacterRanges: createEntryPointFunction(
|
1817
|
+
function(session, containerNode, characterOptions) {
|
1818
|
+
var ranges = this.getAllRanges(), rangeCount = ranges.length;
|
1819
|
+
var rangeInfos = [];
|
1820
|
+
|
1821
|
+
var backward = rangeCount == 1 && this.isBackward();
|
1822
|
+
|
1823
|
+
for (var i = 0, len = ranges.length; i < len; ++i) {
|
1824
|
+
rangeInfos[i] = {
|
1825
|
+
characterRange: ranges[i].toCharacterRange(containerNode, characterOptions),
|
1826
|
+
backward: backward,
|
1827
|
+
characterOptions: characterOptions
|
1828
|
+
};
|
1829
|
+
}
|
1830
|
+
|
1831
|
+
return rangeInfos;
|
1832
|
+
}
|
1833
|
+
),
|
1834
|
+
|
1835
|
+
restoreCharacterRanges: createEntryPointFunction(
|
1836
|
+
function(session, containerNode, saved) {
|
1837
|
+
this.removeAllRanges();
|
1838
|
+
for (var i = 0, len = saved.length, range, rangeInfo, characterRange; i < len; ++i) {
|
1839
|
+
rangeInfo = saved[i];
|
1840
|
+
characterRange = rangeInfo.characterRange;
|
1841
|
+
range = api.createRange(containerNode);
|
1842
|
+
range.selectCharacters(containerNode, characterRange.start, characterRange.end, rangeInfo.characterOptions);
|
1843
|
+
this.addRange(range, rangeInfo.backward);
|
1844
|
+
}
|
1845
|
+
}
|
1846
|
+
),
|
1847
|
+
|
1848
|
+
text: createEntryPointFunction(
|
1849
|
+
function(session, characterOptions) {
|
1850
|
+
var rangeTexts = [];
|
1851
|
+
for (var i = 0, len = this.rangeCount; i < len; ++i) {
|
1852
|
+
rangeTexts[i] = this.getRangeAt(i).text(characterOptions);
|
1853
|
+
}
|
1854
|
+
return rangeTexts.join("");
|
1855
|
+
}
|
1856
|
+
)
|
1857
|
+
});
|
1858
|
+
|
1859
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
1860
|
+
|
1861
|
+
// Extensions to the core rangy object
|
1862
|
+
|
1863
|
+
api.innerText = function(el, characterOptions) {
|
1864
|
+
var range = api.createRange(el);
|
1865
|
+
range.selectNodeContents(el);
|
1866
|
+
var text = range.text(characterOptions);
|
1867
|
+
range.detach();
|
1868
|
+
return text;
|
1869
|
+
};
|
1870
|
+
|
1871
|
+
api.createWordIterator = function(startNode, startOffset, iteratorOptions) {
|
1872
|
+
var session = getSession();
|
1873
|
+
iteratorOptions = createOptions(iteratorOptions, defaultWordIteratorOptions);
|
1874
|
+
var characterOptions = createCharacterOptions(iteratorOptions.characterOptions);
|
1875
|
+
var wordOptions = createWordOptions(iteratorOptions.wordOptions);
|
1876
|
+
var startPos = session.getPosition(startNode, startOffset);
|
1877
|
+
var tokenizedTextProvider = createTokenizedTextProvider(startPos, characterOptions, wordOptions);
|
1878
|
+
var backward = isDirectionBackward(iteratorOptions.direction);
|
1879
|
+
|
1880
|
+
return {
|
1881
|
+
next: function() {
|
1882
|
+
return backward ? tokenizedTextProvider.previousStartToken() : tokenizedTextProvider.nextEndToken();
|
1883
|
+
},
|
1884
|
+
|
1885
|
+
dispose: function() {
|
1886
|
+
tokenizedTextProvider.dispose();
|
1887
|
+
this.next = function() {};
|
1888
|
+
}
|
1889
|
+
};
|
1890
|
+
};
|
1891
|
+
|
1892
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
1893
|
+
|
1894
|
+
api.noMutation = function(func) {
|
1895
|
+
var session = getSession();
|
1896
|
+
func(session);
|
1897
|
+
endSession();
|
1898
|
+
};
|
1899
|
+
|
1900
|
+
api.noMutation.createEntryPointFunction = createEntryPointFunction;
|
1901
|
+
|
1902
|
+
api.textRange = {
|
1903
|
+
isBlockNode: isBlockNode,
|
1904
|
+
isCollapsedWhitespaceNode: isCollapsedWhitespaceNode,
|
1905
|
+
|
1906
|
+
createPosition: createEntryPointFunction(
|
1907
|
+
function(session, node, offset) {
|
1908
|
+
return session.getPosition(node, offset);
|
1909
|
+
}
|
1910
|
+
)
|
1911
|
+
};
|
1912
|
+
});
|