12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982 |
- /**
- * Range封装
- * @file
- * @module UE.dom
- * @class Range
- * @since 1.2.6.1
- */
- /**
- * dom操作封装
- * @unfile
- * @module UE.dom
- */
- /**
- * Range实现类,本类是UEditor底层核心类,封装不同浏览器之间的Range操作。
- * @unfile
- * @module UE.dom
- * @class Range
- */
- (function() {
- var guid = 0,
- fillChar = domUtils.fillChar,
- fillData;
- /**
- * 更新range的collapse状态
- * @param {Range} range range对象
- */
- function updateCollapse(range) {
- range.collapsed =
- range.startContainer &&
- range.endContainer &&
- range.startContainer === range.endContainer &&
- range.startOffset == range.endOffset;
- }
- function selectOneNode(rng) {
- return (
- !rng.collapsed &&
- rng.startContainer.nodeType == 1 &&
- rng.startContainer === rng.endContainer &&
- rng.endOffset - rng.startOffset == 1
- );
- }
- function setEndPoint(toStart, node, offset, range) {
- //如果node是自闭合标签要处理
- if (
- node.nodeType == 1 &&
- (dtd.$empty[node.tagName] || dtd.$nonChild[node.tagName])
- ) {
- offset = domUtils.getNodeIndex(node) + (toStart ? 0 : 1);
- node = node.parentNode;
- }
- if (toStart) {
- range.startContainer = node;
- range.startOffset = offset;
- if (!range.endContainer) {
- range.collapse(true);
- }
- } else {
- range.endContainer = node;
- range.endOffset = offset;
- if (!range.startContainer) {
- range.collapse(false);
- }
- }
- updateCollapse(range);
- return range;
- }
- function execContentsAction(range, action) {
- //调整边界
- //range.includeBookmark();
- var start = range.startContainer,
- end = range.endContainer,
- startOffset = range.startOffset,
- endOffset = range.endOffset,
- doc = range.document,
- frag = doc.createDocumentFragment(),
- tmpStart,
- tmpEnd;
- if (start.nodeType == 1) {
- start =
- start.childNodes[startOffset] ||
- (tmpStart = start.appendChild(doc.createTextNode("")));
- }
- if (end.nodeType == 1) {
- end =
- end.childNodes[endOffset] ||
- (tmpEnd = end.appendChild(doc.createTextNode("")));
- }
- if (start === end && start.nodeType == 3) {
- frag.appendChild(
- doc.createTextNode(
- start.substringData(startOffset, endOffset - startOffset)
- )
- );
- //is not clone
- if (action) {
- start.deleteData(startOffset, endOffset - startOffset);
- range.collapse(true);
- }
- return frag;
- }
- var current,
- currentLevel,
- clone = frag,
- startParents = domUtils.findParents(start, true),
- endParents = domUtils.findParents(end, true);
- for (var i = 0; startParents[i] == endParents[i]; ) {
- i++;
- }
- for (var j = i, si; (si = startParents[j]); j++) {
- current = si.nextSibling;
- if (si == start) {
- if (!tmpStart) {
- if (range.startContainer.nodeType == 3) {
- clone.appendChild(
- doc.createTextNode(start.nodeValue.slice(startOffset))
- );
- //is not clone
- if (action) {
- start.deleteData(
- startOffset,
- start.nodeValue.length - startOffset
- );
- }
- } else {
- clone.appendChild(!action ? start.cloneNode(true) : start);
- }
- }
- } else {
- currentLevel = si.cloneNode(false);
- clone.appendChild(currentLevel);
- }
- while (current) {
- if (current === end || current === endParents[j]) {
- break;
- }
- si = current.nextSibling;
- clone.appendChild(!action ? current.cloneNode(true) : current);
- current = si;
- }
- clone = currentLevel;
- }
- clone = frag;
- if (!startParents[i]) {
- clone.appendChild(startParents[i - 1].cloneNode(false));
- clone = clone.firstChild;
- }
- for (var j = i, ei; (ei = endParents[j]); j++) {
- current = ei.previousSibling;
- if (ei == end) {
- if (!tmpEnd && range.endContainer.nodeType == 3) {
- clone.appendChild(
- doc.createTextNode(end.substringData(0, endOffset))
- );
- //is not clone
- if (action) {
- end.deleteData(0, endOffset);
- }
- }
- } else {
- currentLevel = ei.cloneNode(false);
- clone.appendChild(currentLevel);
- }
- //如果两端同级,右边第一次已经被开始做了
- if (j != i || !startParents[i]) {
- while (current) {
- if (current === start) {
- break;
- }
- ei = current.previousSibling;
- clone.insertBefore(
- !action ? current.cloneNode(true) : current,
- clone.firstChild
- );
- current = ei;
- }
- }
- clone = currentLevel;
- }
- if (action) {
- range
- .setStartBefore(
- !endParents[i]
- ? endParents[i - 1]
- : !startParents[i] ? startParents[i - 1] : endParents[i]
- )
- .collapse(true);
- }
- tmpStart && domUtils.remove(tmpStart);
- tmpEnd && domUtils.remove(tmpEnd);
- return frag;
- }
- /**
- * 创建一个跟document绑定的空的Range实例
- * @constructor
- * @param { Document } document 新建的选区所属的文档对象
- */
- /**
- * @property { Node } startContainer 当前Range的开始边界的容器节点, 可以是一个元素节点或者是文本节点
- */
- /**
- * @property { Node } startOffset 当前Range的开始边界容器节点的偏移量, 如果是元素节点,
- * 该值就是childNodes中的第几个节点, 如果是文本节点就是文本内容的第几个字符
- */
- /**
- * @property { Node } endContainer 当前Range的结束边界的容器节点, 可以是一个元素节点或者是文本节点
- */
- /**
- * @property { Node } endOffset 当前Range的结束边界容器节点的偏移量, 如果是元素节点,
- * 该值就是childNodes中的第几个节点, 如果是文本节点就是文本内容的第几个字符
- */
- /**
- * @property { Boolean } collapsed 当前Range是否闭合
- * @default true
- * @remind Range是闭合的时候, startContainer === endContainer && startOffset === endOffset
- */
- /**
- * @property { Document } document 当前Range所属的Document对象
- * @remind 不同range的的document属性可以是不同的
- */
- var Range = (dom.Range = function(document) {
- var me = this;
- me.startContainer = me.startOffset = me.endContainer = me.endOffset = null;
- me.document = document;
- me.collapsed = true;
- });
- /**
- * 删除fillData
- * @param doc
- * @param excludeNode
- */
- function removeFillData(doc, excludeNode) {
- try {
- if (fillData && domUtils.inDoc(fillData, doc)) {
- if (!fillData.nodeValue.replace(fillCharReg, "").length) {
- var tmpNode = fillData.parentNode;
- domUtils.remove(fillData);
- while (
- tmpNode &&
- domUtils.isEmptyInlineElement(tmpNode) &&
- //safari的contains有bug
- (browser.safari
- ? !(
- domUtils.getPosition(tmpNode, excludeNode) &
- domUtils.POSITION_CONTAINS
- )
- : !tmpNode.contains(excludeNode))
- ) {
- fillData = tmpNode.parentNode;
- domUtils.remove(tmpNode);
- tmpNode = fillData;
- }
- } else {
- fillData.nodeValue = fillData.nodeValue.replace(fillCharReg, "");
- }
- }
- } catch (e) {}
- }
- /**
- * @param node
- * @param dir
- */
- function mergeSibling(node, dir) {
- var tmpNode;
- node = node[dir];
- while (node && domUtils.isFillChar(node)) {
- tmpNode = node[dir];
- domUtils.remove(node);
- node = tmpNode;
- }
- }
- Range.prototype = {
- /**
- * 克隆选区的内容到一个DocumentFragment里
- * @method cloneContents
- * @return { DocumentFragment | NULL } 如果选区是闭合的将返回null, 否则, 返回包含所clone内容的DocumentFragment元素
- * @example
- * ```html
- * <body>
- * <!-- 中括号表示选区 -->
- * <b>x<i>x[x</i>xx]x</b>
- *
- * <script>
- * //range是已选中的选区
- * var fragment = range.cloneContents(),
- * node = document.createElement("div");
- *
- * node.appendChild( fragment );
- *
- * //output: <i>x</i>xx
- * console.log( node.innerHTML );
- *
- * </script>
- * </body>
- * ```
- */
- cloneContents: function() {
- return this.collapsed ? null : execContentsAction(this, 0);
- },
- /**
- * 删除当前选区范围中的所有内容
- * @method deleteContents
- * @remind 执行完该操作后, 当前Range对象变成了闭合状态
- * @return { UE.dom.Range } 当前操作的Range对象
- * @example
- * ```html
- * <body>
- * <!-- 中括号表示选区 -->
- * <b>x<i>x[x</i>xx]x</b>
- *
- * <script>
- * //range是已选中的选区
- * range.deleteContents();
- *
- * //竖线表示闭合后的选区位置
- * //output: <b>x<i>x</i>|x</b>
- * console.log( document.body.innerHTML );
- *
- * //此时, range的各项属性为
- * //output: B
- * console.log( range.startContainer.tagName );
- * //output: 2
- * console.log( range.startOffset );
- * //output: B
- * console.log( range.endContainer.tagName );
- * //output: 2
- * console.log( range.endOffset );
- * //output: true
- * console.log( range.collapsed );
- *
- * </script>
- * </body>
- * ```
- */
- deleteContents: function() {
- var txt;
- if (!this.collapsed) {
- execContentsAction(this, 1);
- }
- if (browser.webkit) {
- txt = this.startContainer;
- if (txt.nodeType == 3 && !txt.nodeValue.length) {
- this.setStartBefore(txt).collapse(true);
- domUtils.remove(txt);
- }
- }
- return this;
- },
- /**
- * 将当前选区的内容提取到一个DocumentFragment里
- * @method extractContents
- * @remind 执行该操作后, 选区将变成闭合状态
- * @warning 执行该操作后, 原来选区所选中的内容将从dom树上剥离出来
- * @return { DocumentFragment } 返回包含所提取内容的DocumentFragment对象
- * @example
- * ```html
- * <body>
- * <!-- 中括号表示选区 -->
- * <b>x<i>x[x</i>xx]x</b>
- *
- * <script>
- * //range是已选中的选区
- * var fragment = range.extractContents(),
- * node = document.createElement( "div" );
- *
- * node.appendChild( fragment );
- *
- * //竖线表示闭合后的选区位置
- *
- * //output: <b>x<i>x</i>|x</b>
- * console.log( document.body.innerHTML );
- * //output: <i>x</i>xx
- * console.log( node.innerHTML );
- *
- * //此时, range的各项属性为
- * //output: B
- * console.log( range.startContainer.tagName );
- * //output: 2
- * console.log( range.startOffset );
- * //output: B
- * console.log( range.endContainer.tagName );
- * //output: 2
- * console.log( range.endOffset );
- * //output: true
- * console.log( range.collapsed );
- *
- * </script>
- * </body>
- */
- extractContents: function() {
- return this.collapsed ? null : execContentsAction(this, 2);
- },
- /**
- * 设置Range的开始容器节点和偏移量
- * @method setStart
- * @remind 如果给定的节点是元素节点,那么offset指的是其子元素中索引为offset的元素,
- * 如果是文本节点,那么offset指的是其文本内容的第offset个字符
- * @remind 如果提供的容器节点是一个不能包含子元素的节点, 则该选区的开始容器将被设置
- * 为该节点的父节点, 此时, 其距离开始容器的偏移量也变成了该节点在其父节点
- * 中的索引
- * @param { Node } node 将被设为当前选区开始边界容器的节点对象
- * @param { int } offset 选区的开始位置偏移量
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- * <!-- 选区 -->
- * <b>xxx<i>x<span>xx</span>xx<em>xx</em>xxx</i>[xxx]</b>
- *
- * <script>
- *
- * //执行操作
- * range.setStart( document.getElementsByTagName("i")[0], 1 );
- *
- * //此时, 选区变成了
- * //<b>xxx<i>x[<span>xx</span>xx<em>xx</em>xxx</i>xxx]</b>
- *
- * </script>
- * ```
- * @example
- * ```html
- * <!-- 选区 -->
- * <b>xxx<img>[xx]x</b>
- *
- * <script>
- *
- * //执行操作
- * range.setStart( document.getElementsByTagName("img")[0], 3 );
- *
- * //此时, 选区变成了
- * //<b>xxx[<img>xx]x</b>
- *
- * </script>
- * ```
- */
- setStart: function(node, offset) {
- return setEndPoint(true, node, offset, this);
- },
- /**
- * 设置Range的结束容器和偏移量
- * @method setEnd
- * @param { Node } node 作为当前选区结束边界容器的节点对象
- * @param { int } offset 结束边界的偏移量
- * @see UE.dom.Range:setStart(Node,int)
- * @return { UE.dom.Range } 当前range对象
- */
- setEnd: function(node, offset) {
- return setEndPoint(false, node, offset, this);
- },
- /**
- * 将Range开始位置设置到node节点之后
- * @method setStartAfter
- * @remind 该操作将会把给定节点的父节点作为range的开始容器, 且偏移量是该节点在其父节点中的位置索引+1
- * @param { Node } node 选区的开始边界将紧接着该节点之后
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- * <!-- 选区示例 -->
- * <b>xx<i>xxx</i><span>xx[x</span>xxx]</b>
- *
- * <script>
- *
- * //执行操作
- * range.setStartAfter( document.getElementsByTagName("i")[0] );
- *
- * //结果选区
- * //<b>xx<i>xxx</i>[<span>xxx</span>xxx]</b>
- *
- * </script>
- * ```
- */
- setStartAfter: function(node) {
- return this.setStart(node.parentNode, domUtils.getNodeIndex(node) + 1);
- },
- /**
- * 将Range开始位置设置到node节点之前
- * @method setStartBefore
- * @remind 该操作将会把给定节点的父节点作为range的开始容器, 且偏移量是该节点在其父节点中的位置索引
- * @param { Node } node 新的选区开始位置在该节点之前
- * @see UE.dom.Range:setStartAfter(Node)
- * @return { UE.dom.Range } 当前range对象
- */
- setStartBefore: function(node) {
- return this.setStart(node.parentNode, domUtils.getNodeIndex(node));
- },
- /**
- * 将Range结束位置设置到node节点之后
- * @method setEndAfter
- * @remind 该操作将会把给定节点的父节点作为range的结束容器, 且偏移量是该节点在其父节点中的位置索引+1
- * @param { Node } node 目标节点
- * @see UE.dom.Range:setStartAfter(Node)
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- * <!-- 选区示例 -->
- * <b>[xx<i>xxx</i><span>xx]x</span>xxx</b>
- *
- * <script>
- *
- * //执行操作
- * range.setStartAfter( document.getElementsByTagName("span")[0] );
- *
- * //结果选区
- * //<b>[xx<i>xxx</i><span>xxx</span>]xxx</b>
- *
- * </script>
- * ```
- */
- setEndAfter: function(node) {
- return this.setEnd(node.parentNode, domUtils.getNodeIndex(node) + 1);
- },
- /**
- * 将Range结束位置设置到node节点之前
- * @method setEndBefore
- * @remind 该操作将会把给定节点的父节点作为range的结束容器, 且偏移量是该节点在其父节点中的位置索引
- * @param { Node } node 目标节点
- * @see UE.dom.Range:setEndAfter(Node)
- * @return { UE.dom.Range } 当前range对象
- */
- setEndBefore: function(node) {
- return this.setEnd(node.parentNode, domUtils.getNodeIndex(node));
- },
- /**
- * 设置Range的开始位置到node节点内的第一个子节点之前
- * @method setStartAtFirst
- * @remind 选区的开始容器将变成给定的节点, 且偏移量为0
- * @remind 如果给定的节点是元素节点, 则该节点必须是允许包含子节点的元素。
- * @param { Node } node 目标节点
- * @see UE.dom.Range:setStartBefore(Node)
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- * <!-- 选区示例 -->
- * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
- *
- * <script>
- *
- * //执行操作
- * range.setStartAtFirst( document.getElementsByTagName("i")[0] );
- *
- * //结果选区
- * //<b>xx<i>[xxx</i><span>xx]x</span>xxx</b>
- *
- * </script>
- * ```
- */
- setStartAtFirst: function(node) {
- return this.setStart(node, 0);
- },
- /**
- * 设置Range的开始位置到node节点内的最后一个节点之后
- * @method setStartAtLast
- * @remind 选区的开始容器将变成给定的节点, 且偏移量为该节点的子节点数
- * @remind 如果给定的节点是元素节点, 则该节点必须是允许包含子节点的元素。
- * @param { Node } node 目标节点
- * @see UE.dom.Range:setStartAtFirst(Node)
- * @return { UE.dom.Range } 当前range对象
- */
- setStartAtLast: function(node) {
- return this.setStart(
- node,
- node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length
- );
- },
- /**
- * 设置Range的结束位置到node节点内的第一个节点之前
- * @method setEndAtFirst
- * @param { Node } node 目标节点
- * @remind 选区的结束容器将变成给定的节点, 且偏移量为0
- * @remind node必须是一个元素节点, 且必须是允许包含子节点的元素。
- * @see UE.dom.Range:setStartAtFirst(Node)
- * @return { UE.dom.Range } 当前range对象
- */
- setEndAtFirst: function(node) {
- return this.setEnd(node, 0);
- },
- /**
- * 设置Range的结束位置到node节点内的最后一个节点之后
- * @method setEndAtLast
- * @param { Node } node 目标节点
- * @remind 选区的结束容器将变成给定的节点, 且偏移量为该节点的子节点数量
- * @remind node必须是一个元素节点, 且必须是允许包含子节点的元素。
- * @see UE.dom.Range:setStartAtFirst(Node)
- * @return { UE.dom.Range } 当前range对象
- */
- setEndAtLast: function(node) {
- return this.setEnd(
- node,
- node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length
- );
- },
- /**
- * 选中给定节点
- * @method selectNode
- * @remind 此时, 选区的开始容器和结束容器都是该节点的父节点, 其startOffset是该节点在父节点中的位置索引,
- * 而endOffset为startOffset+1
- * @param { Node } node 需要选中的节点
- * @return { UE.dom.Range } 当前range对象,此时的range仅包含当前给定的节点对象
- * @example
- * ```html
- * <!-- 选区示例 -->
- * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
- *
- * <script>
- *
- * //执行操作
- * range.selectNode( document.getElementsByTagName("i")[0] );
- *
- * //结果选区
- * //<b>xx[<i>xxx</i>]<span>xxx</span>xxx</b>
- *
- * </script>
- * ```
- */
- selectNode: function(node) {
- return this.setStartBefore(node).setEndAfter(node);
- },
- /**
- * 选中给定节点内部的所有节点
- * @method selectNodeContents
- * @remind 此时, 选区的开始容器和结束容器都是该节点, 其startOffset为0,
- * 而endOffset是该节点的子节点数。
- * @param { Node } node 目标节点, 当前range将包含该节点内的所有节点
- * @return { UE.dom.Range } 当前range对象, 此时range仅包含给定节点的所有子节点
- * @example
- * ```html
- * <!-- 选区示例 -->
- * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
- *
- * <script>
- *
- * //执行操作
- * range.selectNode( document.getElementsByTagName("b")[0] );
- *
- * //结果选区
- * //<b>[xx<i>xxx</i><span>xxx</span>xxx]</b>
- *
- * </script>
- * ```
- */
- selectNodeContents: function(node) {
- return this.setStart(node, 0).setEndAtLast(node);
- },
- /**
- * clone当前Range对象
- * @method cloneRange
- * @remind 返回的range是一个全新的range对象, 其内部所有属性与当前被clone的range相同。
- * @return { UE.dom.Range } 当前range对象的一个副本
- */
- cloneRange: function() {
- var me = this;
- return new Range(me.document)
- .setStart(me.startContainer, me.startOffset)
- .setEnd(me.endContainer, me.endOffset);
- },
- /**
- * 向当前选区的结束处闭合选区
- * @method collapse
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- * <!-- 选区示例 -->
- * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
- *
- * <script>
- *
- * //执行操作
- * range.collapse();
- *
- * //结果选区
- * //“|”表示选区已闭合
- * //<b>xx<i>xxx</i><span>xx|x</span>xxx</b>
- *
- * </script>
- * ```
- */
- /**
- * 闭合当前选区,根据给定的toStart参数项决定是向当前选区开始处闭合还是向结束处闭合,
- * 如果toStart的值为true,则向开始位置闭合, 反之,向结束位置闭合。
- * @method collapse
- * @param { Boolean } toStart 是否向选区开始处闭合
- * @return { UE.dom.Range } 当前range对象,此时range对象处于闭合状态
- * @see UE.dom.Range:collapse()
- * @example
- * ```html
- * <!-- 选区示例 -->
- * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
- *
- * <script>
- *
- * //执行操作
- * range.collapse( true );
- *
- * //结果选区
- * //“|”表示选区已闭合
- * //<b>xx<i>xxx</i><span>|xxx</span>xxx</b>
- *
- * </script>
- * ```
- */
- collapse: function(toStart) {
- var me = this;
- if (toStart) {
- me.endContainer = me.startContainer;
- me.endOffset = me.startOffset;
- } else {
- me.startContainer = me.endContainer;
- me.startOffset = me.endOffset;
- }
- me.collapsed = true;
- return me;
- },
- /**
- * 调整range的开始位置和结束位置,使其"收缩"到最小的位置
- * @method shrinkBoundary
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- * <span>xx<b>xx[</b>xxxxx]</span> => <span>xx<b>xx</b>[xxxxx]</span>
- * ```
- *
- * @example
- * ```html
- * <!-- 选区示例 -->
- * <b>x[xx</b><i>]xxx</i>
- *
- * <script>
- *
- * //执行收缩
- * range.shrinkBoundary();
- *
- * //结果选区
- * //<b>x[xx]</b><i>xxx</i>
- * </script>
- * ```
- *
- * @example
- * ```html
- * [<b><i>xxxx</i>xxxxxxx</b>] => <b><i>[xxxx</i>xxxxxxx]</b>
- * ```
- */
- /**
- * 调整range的开始位置和结束位置,使其"收缩"到最小的位置,
- * 如果ignoreEnd的值为true,则忽略对结束位置的调整
- * @method shrinkBoundary
- * @param { Boolean } ignoreEnd 是否忽略对结束位置的调整
- * @return { UE.dom.Range } 当前range对象
- * @see UE.dom.domUtils.Range:shrinkBoundary()
- */
- shrinkBoundary: function(ignoreEnd) {
- var me = this,
- child,
- collapsed = me.collapsed;
- function check(node) {
- return (
- node.nodeType == 1 &&
- !domUtils.isBookmarkNode(node) &&
- !dtd.$empty[node.tagName] &&
- !dtd.$nonChild[node.tagName]
- );
- }
- while (
- me.startContainer.nodeType == 1 && //是element
- (child = me.startContainer.childNodes[me.startOffset]) && //子节点也是element
- check(child)
- ) {
- me.setStart(child, 0);
- }
- if (collapsed) {
- return me.collapse(true);
- }
- if (!ignoreEnd) {
- while (
- me.endContainer.nodeType == 1 && //是element
- me.endOffset > 0 && //如果是空元素就退出 endOffset=0那么endOffst-1为负值,childNodes[endOffset]报错
- (child = me.endContainer.childNodes[me.endOffset - 1]) && //子节点也是element
- check(child)
- ) {
- me.setEnd(child, child.childNodes.length);
- }
- }
- return me;
- },
- /**
- * 获取离当前选区内包含的所有节点最近的公共祖先节点,
- * @method getCommonAncestor
- * @remind 返回的公共祖先节点一定不是range自身的容器节点, 但有可能是一个文本节点
- * @return { Node } 当前range对象内所有节点的公共祖先节点
- * @example
- * ```html
- * //选区示例
- * <span>xxx<b>x[x<em>xx]x</em>xxx</b>xx</span>
- * <script>
- *
- * var node = range.getCommonAncestor();
- *
- * //公共祖先节点是: b节点
- * //输出: B
- * console.log(node.tagName);
- *
- * </script>
- * ```
- */
- /**
- * 获取当前选区所包含的所有节点的公共祖先节点, 可以根据给定的参数 includeSelf 决定获取到
- * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点, 如果 includeSelf
- * 的取值为true, 则返回的节点可以是自身的容器节点, 否则, 则不能是容器节点
- * @method getCommonAncestor
- * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点
- * @return { Node } 当前range对象内所有节点的公共祖先节点
- * @see UE.dom.Range:getCommonAncestor()
- * @example
- * ```html
- * <body>
- *
- * <!-- 选区示例 -->
- * <b>xxx<i>xxxx<span>xx[x</span>xx]x</i>xxxxxxx</b>
- *
- * <script>
- *
- * var node = range.getCommonAncestor( false );
- *
- * //这里的公共祖先节点是B而不是I, 是因为参数限制了获取到的节点不能是容器节点
- * //output: B
- * console.log( node.tagName );
- *
- * </script>
- *
- * </body>
- * ```
- */
- /**
- * 获取当前选区所包含的所有节点的公共祖先节点, 可以根据给定的参数 includeSelf 决定获取到
- * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点, 如果 includeSelf
- * 的取值为true, 则返回的节点可以是自身的容器节点, 否则, 则不能是容器节点; 同时可以根据
- * ignoreTextNode 参数的取值决定是否忽略类型为文本节点的祖先节点。
- * @method getCommonAncestor
- * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点
- * @param { Boolean } ignoreTextNode 获取祖先节点的过程中是否忽略类型为文本节点的祖先节点
- * @return { Node } 当前range对象内所有节点的公共祖先节点
- * @see UE.dom.Range:getCommonAncestor()
- * @see UE.dom.Range:getCommonAncestor(Boolean)
- * @example
- * ```html
- * <body>
- *
- * <!-- 选区示例 -->
- * <b>xxx<i>xxxx<span>x[x]x</span>xxx</i>xxxxxxx</b>
- *
- * <script>
- *
- * var node = range.getCommonAncestor( true, false );
- *
- * //output: SPAN
- * console.log( node.tagName );
- *
- * </script>
- *
- * </body>
- * ```
- */
- getCommonAncestor: function(includeSelf, ignoreTextNode) {
- var me = this,
- start = me.startContainer,
- end = me.endContainer;
- if (start === end) {
- if (includeSelf && selectOneNode(this)) {
- start = start.childNodes[me.startOffset];
- if (start.nodeType == 1) return start;
- }
- //只有在上来就相等的情况下才会出现是文本的情况
- return ignoreTextNode && start.nodeType == 3 ? start.parentNode : start;
- }
- return domUtils.getCommonAncestor(start, end);
- },
- /**
- * 调整当前Range的开始和结束边界容器,如果是容器节点是文本节点,就调整到包含该文本节点的父节点上
- * @method trimBoundary
- * @remind 该操作有可能会引起文本节点被切开
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- *
- * //选区示例
- * <b>xxx<i>[xxxxx]</i>xxx</b>
- *
- * <script>
- * //未调整前, 选区的开始容器和结束都是文本节点
- * //执行调整
- * range.trimBoundary();
- *
- * //调整之后, 容器节点变成了i节点
- * //<b>xxx[<i>xxxxx</i>]xxx</b>
- * </script>
- * ```
- */
- /**
- * 调整当前Range的开始和结束边界容器,如果是容器节点是文本节点,就调整到包含该文本节点的父节点上,
- * 可以根据 ignoreEnd 参数的值决定是否调整对结束边界的调整
- * @method trimBoundary
- * @param { Boolean } ignoreEnd 是否忽略对结束边界的调整
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- *
- * //选区示例
- * <b>xxx<i>[xxxxx]</i>xxx</b>
- *
- * <script>
- * //未调整前, 选区的开始容器和结束都是文本节点
- * //执行调整
- * range.trimBoundary( true );
- *
- * //调整之后, 开始容器节点变成了i节点
- * //但是, 结束容器没有发生变化
- * //<b>xxx[<i>xxxxx]</i>xxx</b>
- * </script>
- * ```
- */
- trimBoundary: function(ignoreEnd) {
- this.txtToElmBoundary();
- var start = this.startContainer,
- offset = this.startOffset,
- collapsed = this.collapsed,
- end = this.endContainer;
- if (start.nodeType == 3) {
- if (offset == 0) {
- this.setStartBefore(start);
- } else {
- if (offset >= start.nodeValue.length) {
- this.setStartAfter(start);
- } else {
- var textNode = domUtils.split(start, offset);
- //跟新结束边界
- if (start === end) {
- this.setEnd(textNode, this.endOffset - offset);
- } else if (start.parentNode === end) {
- this.endOffset += 1;
- }
- this.setStartBefore(textNode);
- }
- }
- if (collapsed) {
- return this.collapse(true);
- }
- }
- if (!ignoreEnd) {
- offset = this.endOffset;
- end = this.endContainer;
- if (end.nodeType == 3) {
- if (offset == 0) {
- this.setEndBefore(end);
- } else {
- offset < end.nodeValue.length && domUtils.split(end, offset);
- this.setEndAfter(end);
- }
- }
- }
- return this;
- },
- /**
- * 如果选区在文本的边界上,就扩展选区到文本的父节点上, 如果当前选区是闭合的, 则什么也不做
- * @method txtToElmBoundary
- * @remind 该操作不会修改dom节点
- * @return { UE.dom.Range } 当前range对象
- */
- /**
- * 如果选区在文本的边界上,就扩展选区到文本的父节点上, 如果当前选区是闭合的, 则根据参数项
- * ignoreCollapsed 的值决定是否执行该调整
- * @method txtToElmBoundary
- * @param { Boolean } ignoreCollapsed 是否忽略选区的闭合状态, 如果该参数取值为true, 则
- * 不论选区是否闭合, 都会执行该操作, 反之, 则不会对闭合的选区执行该操作
- * @return { UE.dom.Range } 当前range对象
- */
- txtToElmBoundary: function(ignoreCollapsed) {
- function adjust(r, c) {
- var container = r[c + "Container"],
- offset = r[c + "Offset"];
- if (container.nodeType == 3) {
- if (!offset) {
- r[
- "set" +
- c.replace(/(\w)/, function(a) {
- return a.toUpperCase();
- }) +
- "Before"
- ](container);
- } else if (offset >= container.nodeValue.length) {
- r[
- "set" +
- c.replace(/(\w)/, function(a) {
- return a.toUpperCase();
- }) +
- "After"
- ](container);
- }
- }
- }
- if (ignoreCollapsed || !this.collapsed) {
- adjust(this, "start");
- adjust(this, "end");
- }
- return this;
- },
- /**
- * 在当前选区的开始位置前插入节点,新插入的节点会被该range包含
- * @method insertNode
- * @param { Node } node 需要插入的节点
- * @remind 插入的节点可以是一个DocumentFragment依次插入多个节点
- * @return { UE.dom.Range } 当前range对象
- */
- insertNode: function(node) {
- var first = node,
- length = 1;
- if (node.nodeType == 11) {
- first = node.firstChild;
- length = node.childNodes.length;
- }
- this.trimBoundary(true);
- var start = this.startContainer,
- offset = this.startOffset;
- var nextNode = start.childNodes[offset];
- if (nextNode) {
- start.insertBefore(node, nextNode);
- } else {
- start.appendChild(node);
- }
- if (first.parentNode === this.endContainer) {
- this.endOffset = this.endOffset + length;
- }
- return this.setStartBefore(first);
- },
- /**
- * 闭合选区到当前选区的开始位置, 并且定位光标到闭合后的位置
- * @method setCursor
- * @return { UE.dom.Range } 当前range对象
- * @see UE.dom.Range:collapse()
- */
- /**
- * 闭合选区,可以根据参数toEnd的值控制选区是向前闭合还是向后闭合, 并且定位光标到闭合后的位置。
- * @method setCursor
- * @param { Boolean } toEnd 是否向后闭合, 如果为true, 则闭合选区时, 将向结束容器方向闭合,
- * 反之,则向开始容器方向闭合
- * @return { UE.dom.Range } 当前range对象
- * @see UE.dom.Range:collapse(Boolean)
- */
- setCursor: function(toEnd, noFillData) {
- return this.collapse(!toEnd).select(noFillData);
- },
- /**
- * 创建当前range的一个书签,记录下当前range的位置,方便当dom树改变时,还能找回原来的选区位置
- * @method createBookmark
- * @param { Boolean } serialize 控制返回的标记位置是对当前位置的引用还是ID,如果该值为true,则
- * 返回标记位置的ID, 反之则返回标记位置节点的引用
- * @return { Object } 返回一个书签记录键值对, 其包含的key有: start => 开始标记的ID或者引用,
- * end => 结束标记的ID或引用, id => 当前标记的类型, 如果为true,则表示
- * 返回的记录的类型为ID, 反之则为引用
- */
- createBookmark: function(serialize, same) {
- var endNode,
- startNode = this.document.createElement("span");
- startNode.style.cssText = "display:none;line-height:0px;";
- startNode.appendChild(this.document.createTextNode("\u200D"));
- startNode.id = "_baidu_bookmark_start_" + (same ? "" : guid++);
- if (!this.collapsed) {
- endNode = startNode.cloneNode(true);
- endNode.id = "_baidu_bookmark_end_" + (same ? "" : guid++);
- }
- this.insertNode(startNode);
- if (endNode) {
- this.collapse().insertNode(endNode).setEndBefore(endNode);
- }
- this.setStartAfter(startNode);
- return {
- start: serialize ? startNode.id : startNode,
- end: endNode ? (serialize ? endNode.id : endNode) : null,
- id: serialize
- };
- },
- /**
- * 调整当前range的边界到书签位置,并删除该书签对象所标记的位置内的节点
- * @method moveToBookmark
- * @param { BookMark } bookmark createBookmark所创建的标签对象
- * @return { UE.dom.Range } 当前range对象
- * @see UE.dom.Range:createBookmark(Boolean)
- */
- moveToBookmark: function(bookmark) {
- var start = bookmark.id
- ? this.document.getElementById(bookmark.start)
- : bookmark.start,
- end = bookmark.end && bookmark.id
- ? this.document.getElementById(bookmark.end)
- : bookmark.end;
- this.setStartBefore(start);
- domUtils.remove(start);
- if (end) {
- this.setEndBefore(end);
- domUtils.remove(end);
- } else {
- this.collapse(true);
- }
- return this;
- },
- /**
- * 调整range的边界,使其"放大"到最近的父节点
- * @method enlarge
- * @remind 会引起选区的变化
- * @return { UE.dom.Range } 当前range对象
- */
- /**
- * 调整range的边界,使其"放大"到最近的父节点,根据参数 toBlock 的取值, 可以
- * 要求扩大之后的父节点是block节点
- * @method enlarge
- * @param { Boolean } toBlock 是否要求扩大之后的父节点必须是block节点
- * @return { UE.dom.Range } 当前range对象
- */
- enlarge: function(toBlock, stopFn) {
- var isBody = domUtils.isBody,
- pre,
- node,
- tmp = this.document.createTextNode("");
- if (toBlock) {
- node = this.startContainer;
- if (node.nodeType == 1) {
- if (node.childNodes[this.startOffset]) {
- pre = node = node.childNodes[this.startOffset];
- } else {
- node.appendChild(tmp);
- pre = node = tmp;
- }
- } else {
- pre = node;
- }
- while (1) {
- if (domUtils.isBlockElm(node)) {
- node = pre;
- while ((pre = node.previousSibling) && !domUtils.isBlockElm(pre)) {
- node = pre;
- }
- this.setStartBefore(node);
- break;
- }
- pre = node;
- node = node.parentNode;
- }
- node = this.endContainer;
- if (node.nodeType == 1) {
- if ((pre = node.childNodes[this.endOffset])) {
- node.insertBefore(tmp, pre);
- } else {
- node.appendChild(tmp);
- }
- pre = node = tmp;
- } else {
- pre = node;
- }
- while (1) {
- if (domUtils.isBlockElm(node)) {
- node = pre;
- while ((pre = node.nextSibling) && !domUtils.isBlockElm(pre)) {
- node = pre;
- }
- this.setEndAfter(node);
- break;
- }
- pre = node;
- node = node.parentNode;
- }
- if (tmp.parentNode === this.endContainer) {
- this.endOffset--;
- }
- domUtils.remove(tmp);
- }
- // 扩展边界到最大
- if (!this.collapsed) {
- while (this.startOffset == 0) {
- if (stopFn && stopFn(this.startContainer)) {
- break;
- }
- if (isBody(this.startContainer)) {
- break;
- }
- this.setStartBefore(this.startContainer);
- }
- while (
- this.endOffset ==
- (this.endContainer.nodeType == 1
- ? this.endContainer.childNodes.length
- : this.endContainer.nodeValue.length)
- ) {
- if (stopFn && stopFn(this.endContainer)) {
- break;
- }
- if (isBody(this.endContainer)) {
- break;
- }
- this.setEndAfter(this.endContainer);
- }
- }
- return this;
- },
- enlargeToBlockElm: function(ignoreEnd) {
- while (!domUtils.isBlockElm(this.startContainer)) {
- this.setStartBefore(this.startContainer);
- }
- if (!ignoreEnd) {
- while (!domUtils.isBlockElm(this.endContainer)) {
- this.setEndAfter(this.endContainer);
- }
- }
- return this;
- },
- /**
- * 调整Range的边界,使其"缩小"到最合适的位置
- * @method adjustmentBoundary
- * @return { UE.dom.Range } 当前range对象
- * @see UE.dom.Range:shrinkBoundary()
- */
- adjustmentBoundary: function() {
- if (!this.collapsed) {
- while (
- !domUtils.isBody(this.startContainer) &&
- this.startOffset ==
- this.startContainer[
- this.startContainer.nodeType == 3 ? "nodeValue" : "childNodes"
- ].length &&
- this.startContainer[
- this.startContainer.nodeType == 3 ? "nodeValue" : "childNodes"
- ].length
- ) {
- this.setStartAfter(this.startContainer);
- }
- while (
- !domUtils.isBody(this.endContainer) &&
- !this.endOffset &&
- this.endContainer[
- this.endContainer.nodeType == 3 ? "nodeValue" : "childNodes"
- ].length
- ) {
- this.setEndBefore(this.endContainer);
- }
- }
- return this;
- },
- /**
- * 给range选区中的内容添加给定的inline标签
- * @method applyInlineStyle
- * @param { String } tagName 需要添加的标签名
- * @example
- * ```html
- * <p>xxxx[xxxx]x</p> ==> range.applyInlineStyle("strong") ==> <p>xxxx[<strong>xxxx</strong>]x</p>
- * ```
- */
- /**
- * 给range选区中的内容添加给定的inline标签, 并且为标签附加上一些初始化属性。
- * @method applyInlineStyle
- * @param { String } tagName 需要添加的标签名
- * @param { Object } attrs 跟随新添加的标签的属性
- * @return { UE.dom.Range } 当前选区
- * @example
- * ```html
- * <p>xxxx[xxxx]x</p>
- *
- * ==>
- *
- * <!-- 执行操作 -->
- * range.applyInlineStyle("strong",{"style":"font-size:12px"})
- *
- * ==>
- *
- * <p>xxxx[<strong style="font-size:12px">xxxx</strong>]x</p>
- * ```
- */
- applyInlineStyle: function(tagName, attrs, list) {
- if (this.collapsed) return this;
- this.trimBoundary()
- .enlarge(false, function(node) {
- return node.nodeType == 1 && domUtils.isBlockElm(node);
- })
- .adjustmentBoundary();
- var bookmark = this.createBookmark(),
- end = bookmark.end,
- filterFn = function(node) {
- return node.nodeType == 1
- ? node.tagName.toLowerCase() != "br"
- : !domUtils.isWhitespace(node);
- },
- current = domUtils.getNextDomNode(bookmark.start, false, filterFn),
- node,
- pre,
- range = this.cloneRange();
- while (
- current &&
- domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING
- ) {
- if (current.nodeType == 3 || dtd[tagName][current.tagName]) {
- range.setStartBefore(current);
- node = current;
- while (
- node &&
- (node.nodeType == 3 || dtd[tagName][node.tagName]) &&
- node !== end
- ) {
- pre = node;
- node = domUtils.getNextDomNode(
- node,
- node.nodeType == 1,
- null,
- function(parent) {
- return dtd[tagName][parent.tagName];
- }
- );
- }
- var frag = range.setEndAfter(pre).extractContents(),
- elm;
- if (list && list.length > 0) {
- var level, top;
- top = level = list[0].cloneNode(false);
- for (var i = 1, ci; (ci = list[i++]); ) {
- level.appendChild(ci.cloneNode(false));
- level = level.firstChild;
- }
- elm = level;
- } else {
- elm = range.document.createElement(tagName);
- }
- if (attrs) {
- domUtils.setAttributes(elm, attrs);
- }
- elm.appendChild(frag);
- //针对嵌套span的全局样式指定,做容错处理
- if (elm.tagName == "SPAN" && attrs && attrs.style) {
- utils.each(elm.getElementsByTagName("span"), function(s) {
- s.style.cssText = s.style.cssText + ";" + attrs.style;
- });
- }
- range.insertNode(list ? top : elm);
- //处理下滑线在a上的情况
- var aNode;
- if (
- tagName == "span" &&
- attrs.style &&
- /text\-decoration/.test(attrs.style) &&
- (aNode = domUtils.findParentByTagName(elm, "a", true))
- ) {
- domUtils.setAttributes(aNode, attrs);
- domUtils.remove(elm, true);
- elm = aNode;
- } else {
- domUtils.mergeSibling(elm);
- domUtils.clearEmptySibling(elm);
- }
- //去除子节点相同的
- domUtils.mergeChild(elm, attrs);
- current = domUtils.getNextDomNode(elm, false, filterFn);
- domUtils.mergeToParent(elm);
- if (node === end) {
- break;
- }
- } else {
- current = domUtils.getNextDomNode(current, true, filterFn);
- }
- }
- return this.moveToBookmark(bookmark);
- },
- /**
- * 移除当前选区内指定的inline标签,但保留其中的内容
- * @method removeInlineStyle
- * @param { String } tagName 需要移除的标签名
- * @return { UE.dom.Range } 当前的range对象
- * @example
- * ```html
- * xx[x<span>xxx<em>yyy</em>zz]z</span> => range.removeInlineStyle(["em"]) => xx[x<span>xxxyyyzz]z</span>
- * ```
- */
- /**
- * 移除当前选区内指定的一组inline标签,但保留其中的内容
- * @method removeInlineStyle
- * @param { Array } tagNameArr 需要移除的标签名的数组
- * @return { UE.dom.Range } 当前的range对象
- * @see UE.dom.Range:removeInlineStyle(String)
- */
- removeInlineStyle: function(tagNames) {
- if (this.collapsed) return this;
- tagNames = utils.isArray(tagNames) ? tagNames : [tagNames];
- this.shrinkBoundary().adjustmentBoundary();
- var start = this.startContainer,
- end = this.endContainer;
- while (1) {
- if (start.nodeType == 1) {
- if (utils.indexOf(tagNames, start.tagName.toLowerCase()) > -1) {
- break;
- }
- if (start.tagName.toLowerCase() == "body") {
- start = null;
- break;
- }
- }
- start = start.parentNode;
- }
- while (1) {
- if (end.nodeType == 1) {
- if (utils.indexOf(tagNames, end.tagName.toLowerCase()) > -1) {
- break;
- }
- if (end.tagName.toLowerCase() == "body") {
- end = null;
- break;
- }
- }
- end = end.parentNode;
- }
- var bookmark = this.createBookmark(),
- frag,
- tmpRange;
- if (start) {
- tmpRange = this.cloneRange()
- .setEndBefore(bookmark.start)
- .setStartBefore(start);
- frag = tmpRange.extractContents();
- tmpRange.insertNode(frag);
- domUtils.clearEmptySibling(start, true);
- start.parentNode.insertBefore(bookmark.start, start);
- }
- if (end) {
- tmpRange = this.cloneRange()
- .setStartAfter(bookmark.end)
- .setEndAfter(end);
- frag = tmpRange.extractContents();
- tmpRange.insertNode(frag);
- domUtils.clearEmptySibling(end, false, true);
- end.parentNode.insertBefore(bookmark.end, end.nextSibling);
- }
- var current = domUtils.getNextDomNode(bookmark.start, false, function(
- node
- ) {
- return node.nodeType == 1;
- }),
- next;
- while (current && current !== bookmark.end) {
- next = domUtils.getNextDomNode(current, true, function(node) {
- return node.nodeType == 1;
- });
- if (utils.indexOf(tagNames, current.tagName.toLowerCase()) > -1) {
- domUtils.remove(current, true);
- }
- current = next;
- }
- return this.moveToBookmark(bookmark);
- },
- /**
- * 获取当前选中的自闭合的节点
- * @method getClosedNode
- * @return { Node | NULL } 如果当前选中的是自闭合节点, 则返回该节点, 否则返回NULL
- */
- getClosedNode: function() {
- var node;
- if (!this.collapsed) {
- var range = this.cloneRange().adjustmentBoundary().shrinkBoundary();
- if (selectOneNode(range)) {
- var child = range.startContainer.childNodes[range.startOffset];
- if (
- child &&
- child.nodeType == 1 &&
- (dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName])
- ) {
- node = child;
- }
- }
- }
- return node;
- },
- /**
- * 在页面上高亮range所表示的选区
- * @method select
- * @return { UE.dom.Range } 返回当前Range对象
- */
- //这里不区分ie9以上,trace:3824
- select: browser.ie
- ? function(noFillData, textRange) {
- var nativeRange;
- if (!this.collapsed) this.shrinkBoundary();
- var node = this.getClosedNode();
- if (node && !textRange) {
- try {
- nativeRange = this.document.body.createControlRange();
- nativeRange.addElement(node);
- nativeRange.select();
- } catch (e) {}
- return this;
- }
- var bookmark = this.createBookmark(),
- start = bookmark.start,
- end;
- nativeRange = this.document.body.createTextRange();
- nativeRange.moveToElementText(start);
- nativeRange.moveStart("character", 1);
- if (!this.collapsed) {
- var nativeRangeEnd = this.document.body.createTextRange();
- end = bookmark.end;
- nativeRangeEnd.moveToElementText(end);
- nativeRange.setEndPoint("EndToEnd", nativeRangeEnd);
- } else {
- if (!noFillData && this.startContainer.nodeType != 3) {
- //使用<span>|x<span>固定住光标
- var tmpText = this.document.createTextNode(fillChar),
- tmp = this.document.createElement("span");
- tmp.appendChild(this.document.createTextNode(fillChar));
- start.parentNode.insertBefore(tmp, start);
- start.parentNode.insertBefore(tmpText, start);
- //当点b,i,u时,不能清除i上边的b
- removeFillData(this.document, tmpText);
- fillData = tmpText;
- mergeSibling(tmp, "previousSibling");
- mergeSibling(start, "nextSibling");
- nativeRange.moveStart("character", -1);
- nativeRange.collapse(true);
- }
- }
- this.moveToBookmark(bookmark);
- tmp && domUtils.remove(tmp);
- //IE在隐藏状态下不支持range操作,catch一下
- try {
- nativeRange.select();
- } catch (e) {}
- return this;
- }
- : function(notInsertFillData) {
- function checkOffset(rng) {
- function check(node, offset, dir) {
- if (node.nodeType == 3 && node.nodeValue.length < offset) {
- rng[dir + "Offset"] = node.nodeValue.length;
- }
- }
- check(rng.startContainer, rng.startOffset, "start");
- check(rng.endContainer, rng.endOffset, "end");
- }
- var win = domUtils.getWindow(this.document),
- sel = win.getSelection(),
- txtNode;
- //FF下关闭自动长高时滚动条在关闭dialog时会跳
- //ff下如果不body.focus将不能定位闭合光标到编辑器内
- browser.gecko ? this.document.body.focus() : win.focus();
- if (sel) {
- sel.removeAllRanges();
- // trace:870 chrome/safari后边是br对于闭合得range不能定位 所以去掉了判断
- // this.startContainer.nodeType != 3 &&! ((child = this.startContainer.childNodes[this.startOffset]) && child.nodeType == 1 && child.tagName == 'BR'
- if (this.collapsed && !notInsertFillData) {
- // //opear如果没有节点接着,原生的不能够定位,不能在body的第一级插入空白节点
- // if (notInsertFillData && browser.opera && !domUtils.isBody(this.startContainer) && this.startContainer.nodeType == 1) {
- // var tmp = this.document.createTextNode('');
- // this.insertNode(tmp).setStart(tmp, 0).collapse(true);
- // }
- //
- //处理光标落在文本节点的情况
- //处理以下的情况
- //<b>|xxxx</b>
- //<b>xxxx</b>|xxxx
- //xxxx<b>|</b>
- var start = this.startContainer,
- child = start;
- if (start.nodeType == 1) {
- child = start.childNodes[this.startOffset];
- }
- if (
- !(start.nodeType == 3 && this.startOffset) &&
- (child
- ? !child.previousSibling ||
- child.previousSibling.nodeType != 3
- : !start.lastChild || start.lastChild.nodeType != 3)
- ) {
- txtNode = this.document.createTextNode(fillChar);
- //跟着前边走
- this.insertNode(txtNode);
- removeFillData(this.document, txtNode);
- mergeSibling(txtNode, "previousSibling");
- mergeSibling(txtNode, "nextSibling");
- fillData = txtNode;
- this.setStart(txtNode, browser.webkit ? 1 : 0).collapse(true);
- }
- }
- var nativeRange = this.document.createRange();
- if (
- this.collapsed &&
- browser.opera &&
- this.startContainer.nodeType == 1
- ) {
- var child = this.startContainer.childNodes[this.startOffset];
- if (!child) {
- //往前靠拢
- child = this.startContainer.lastChild;
- if (child && domUtils.isBr(child)) {
- this.setStartBefore(child).collapse(true);
- }
- } else {
- //向后靠拢
- while (child && domUtils.isBlockElm(child)) {
- if (child.nodeType == 1 && child.childNodes[0]) {
- child = child.childNodes[0];
- } else {
- break;
- }
- }
- child && this.setStartBefore(child).collapse(true);
- }
- }
- //是createAddress最后一位算的不准,现在这里进行微调
- checkOffset(this);
- nativeRange.setStart(this.startContainer, this.startOffset);
- nativeRange.setEnd(this.endContainer, this.endOffset);
- sel.addRange(nativeRange);
- }
- return this;
- },
- /**
- * 滚动到当前range开始的位置
- * @method scrollToView
- * @param { Window } win 当前range对象所属的window对象
- * @return { UE.dom.Range } 当前Range对象
- */
- /**
- * 滚动到距离当前range开始位置 offset 的位置处
- * @method scrollToView
- * @param { Window } win 当前range对象所属的window对象
- * @param { Number } offset 距离range开始位置处的偏移量, 如果为正数, 则向下偏移, 反之, 则向上偏移
- * @return { UE.dom.Range } 当前Range对象
- */
- scrollToView: function(win, offset) {
- win = win ? window : domUtils.getWindow(this.document);
- var me = this,
- span = me.document.createElement("span");
- //trace:717
- span.innerHTML = " ";
- me.cloneRange().insertNode(span);
- domUtils.scrollToView(span, win, offset);
- domUtils.remove(span);
- return me;
- },
- /**
- * 判断当前选区内容是否占位符
- * @private
- * @method inFillChar
- * @return { Boolean } 如果是占位符返回true,否则返回false
- */
- inFillChar: function() {
- var start = this.startContainer;
- if (
- this.collapsed &&
- start.nodeType == 3 &&
- start.nodeValue.replace(new RegExp("^" + domUtils.fillChar), "")
- .length +
- 1 ==
- start.nodeValue.length
- ) {
- return true;
- }
- return false;
- },
- /**
- * 保存
- * @method createAddress
- * @private
- * @return { Boolean } 返回开始和结束的位置
- * @example
- * ```html
- * <body>
- * <p>
- * aaaa
- * <em>
- * <!-- 选区开始 -->
- * bbbb
- * <!-- 选区结束 -->
- * </em>
- * </p>
- *
- * <script>
- * //output: {startAddress:[0,1,0,0],endAddress:[0,1,0,4]}
- * console.log( range.createAddress() );
- * </script>
- * </body>
- * ```
- */
- createAddress: function(ignoreEnd, ignoreTxt) {
- var addr = {},
- me = this;
- function getAddress(isStart) {
- var node = isStart ? me.startContainer : me.endContainer;
- var parents = domUtils.findParents(node, true, function(node) {
- return !domUtils.isBody(node);
- }),
- addrs = [];
- for (var i = 0, ci; (ci = parents[i++]); ) {
- addrs.push(domUtils.getNodeIndex(ci, ignoreTxt));
- }
- var firstIndex = 0;
- if (ignoreTxt) {
- if (node.nodeType == 3) {
- var tmpNode = node.previousSibling;
- while (tmpNode && tmpNode.nodeType == 3) {
- firstIndex += tmpNode.nodeValue.replace(fillCharReg, "").length;
- tmpNode = tmpNode.previousSibling;
- }
- firstIndex += isStart ? me.startOffset : me.endOffset; // - (fillCharReg.test(node.nodeValue) ? 1 : 0 )
- } else {
- node = node.childNodes[isStart ? me.startOffset : me.endOffset];
- if (node) {
- firstIndex = domUtils.getNodeIndex(node, ignoreTxt);
- } else {
- node = isStart ? me.startContainer : me.endContainer;
- var first = node.firstChild;
- while (first) {
- if (domUtils.isFillChar(first)) {
- first = first.nextSibling;
- continue;
- }
- firstIndex++;
- if (first.nodeType == 3) {
- while (first && first.nodeType == 3) {
- first = first.nextSibling;
- }
- } else {
- first = first.nextSibling;
- }
- }
- }
- }
- } else {
- firstIndex = isStart
- ? domUtils.isFillChar(node) ? 0 : me.startOffset
- : me.endOffset;
- }
- if (firstIndex < 0) {
- firstIndex = 0;
- }
- addrs.push(firstIndex);
- return addrs;
- }
- addr.startAddress = getAddress(true);
- if (!ignoreEnd) {
- addr.endAddress = me.collapsed
- ? [].concat(addr.startAddress)
- : getAddress();
- }
- return addr;
- },
- /**
- * 保存
- * @method createAddress
- * @private
- * @return { Boolean } 返回开始和结束的位置
- * @example
- * ```html
- * <body>
- * <p>
- * aaaa
- * <em>
- * <!-- 选区开始 -->
- * bbbb
- * <!-- 选区结束 -->
- * </em>
- * </p>
- *
- * <script>
- * var range = editor.selection.getRange();
- * range.moveToAddress({startAddress:[0,1,0,0],endAddress:[0,1,0,4]});
- * range.select();
- * //output: 'bbbb'
- * console.log(editor.selection.getText());
- * </script>
- * </body>
- * ```
- */
- moveToAddress: function(addr, ignoreEnd) {
- var me = this;
- function getNode(address, isStart) {
- var tmpNode = me.document.body,
- parentNode,
- offset;
- for (var i = 0, ci, l = address.length; i < l; i++) {
- ci = address[i];
- parentNode = tmpNode;
- tmpNode = tmpNode.childNodes[ci];
- if (!tmpNode) {
- offset = ci;
- break;
- }
- }
- if (isStart) {
- if (tmpNode) {
- me.setStartBefore(tmpNode);
- } else {
- me.setStart(parentNode, offset);
- }
- } else {
- if (tmpNode) {
- me.setEndBefore(tmpNode);
- } else {
- me.setEnd(parentNode, offset);
- }
- }
- }
- getNode(addr.startAddress, true);
- !ignoreEnd && addr.endAddress && getNode(addr.endAddress);
- return me;
- },
- /**
- * 判断给定的Range对象是否和当前Range对象表示的是同一个选区
- * @method equals
- * @param { UE.dom.Range } 需要判断的Range对象
- * @return { Boolean } 如果给定的Range对象与当前Range对象表示的是同一个选区, 则返回true, 否则返回false
- */
- equals: function(rng) {
- for (var p in this) {
- if (this.hasOwnProperty(p)) {
- if (this[p] !== rng[p]) return false;
- }
- }
- return true;
- },
- /**
- * 遍历range内的节点。每当遍历一个节点时, 都会执行参数项 doFn 指定的函数, 该函数的接受当前遍历的节点
- * 作为其参数。
- * @method traversal
- * @param { Function } doFn 对每个遍历的节点要执行的方法, 该方法接受当前遍历的节点作为其参数
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- *
- * <body>
- *
- * <!-- 选区开始 -->
- * <span></span>
- * <a></a>
- * <!-- 选区结束 -->
- * </body>
- *
- * <script>
- *
- * //output: <span></span><a></a>
- * console.log( range.cloneContents() );
- *
- * range.traversal( function ( node ) {
- *
- * if ( node.nodeType === 1 ) {
- * node.className = "test";
- * }
- *
- * } );
- *
- * //output: <span class="test"></span><a class="test"></a>
- * console.log( range.cloneContents() );
- *
- * </script>
- * ```
- */
- /**
- * 遍历range内的节点。
- * 每当遍历一个节点时, 都会执行参数项 doFn 指定的函数, 该函数的接受当前遍历的节点
- * 作为其参数。
- * 可以通过参数项 filterFn 来指定一个过滤器, 只有符合该过滤器过滤规则的节点才会触
- * 发doFn函数的执行
- * @method traversal
- * @param { Function } doFn 对每个遍历的节点要执行的方法, 该方法接受当前遍历的节点作为其参数
- * @param { Function } filterFn 过滤器, 该函数接受当前遍历的节点作为参数, 如果该节点满足过滤
- * 规则, 请返回true, 该节点会触发doFn, 否则, 请返回false, 则该节点不
- * 会触发doFn。
- * @return { UE.dom.Range } 当前range对象
- * @see UE.dom.Range:traversal(Function)
- * @example
- * ```html
- *
- * <body>
- *
- * <!-- 选区开始 -->
- * <span></span>
- * <a></a>
- * <!-- 选区结束 -->
- * </body>
- *
- * <script>
- *
- * //output: <span></span><a></a>
- * console.log( range.cloneContents() );
- *
- * range.traversal( function ( node ) {
- *
- * node.className = "test";
- *
- * }, function ( node ) {
- * return node.nodeType === 1;
- * } );
- *
- * //output: <span class="test"></span><a class="test"></a>
- * console.log( range.cloneContents() );
- *
- * </script>
- * ```
- */
- traversal: function(doFn, filterFn) {
- if (this.collapsed) return this;
- var bookmark = this.createBookmark(),
- end = bookmark.end,
- current = domUtils.getNextDomNode(bookmark.start, false, filterFn);
- while (
- current &&
- current !== end &&
- domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING
- ) {
- var tmpNode = domUtils.getNextDomNode(current, false, filterFn);
- doFn(current);
- current = tmpNode;
- }
- return this.moveToBookmark(bookmark);
- }
- };
- })();
|