/** * SmartEditor2 NAVER_Library:SE2.8.2.O4259f59 * Copyright NAVER Corp. Licensed under LGPL v2 * See license text at http://dev.naver.com/projects/smarteditor/wiki/LICENSE */ if(typeof window.nhn=='undefined'){window.nhn = {};} if (!nhn.husky){nhn.husky = {};} /** * @fileOverview This file contains Husky framework core * @name HuskyCore.js */ (function(){ var _rxMsgHandler = /^\$(LOCAL|BEFORE|ON|AFTER)_/, _rxMsgAppReady = /^\$(BEFORE|ON|AFTER)_MSG_APP_READY$/, _aHuskyCores = [], // HuskyCore instance list _htLoadedFile = {}; // lazy-loaded file list nhn.husky.HuskyCore = jindo.$Class({ name : "HuskyCore", aCallerStack : null, bMobile : jindo.$Agent().navigator().mobile || jindo.$Agent().navigator().msafari, $init : function(htOptions){ this.htOptions = htOptions||{}; _aHuskyCores.push(this); if( this.htOptions.oDebugger ){ nhn.husky.HuskyCore.getCore = function() { return _aHuskyCores; }; this.htOptions.oDebugger.setApp(this); } // To prevent processing a Husky message before all the plugins are registered and ready, // Queue up all the messages here until the application's status is changed to READY this.messageQueue = []; this.oMessageMap = {}; this.oDisabledMessage = {}; this.oLazyMessage = {}; this.aPlugins = []; this.appStatus = nhn.husky.APP_STATUS.NOT_READY; this.aCallerStack = []; this._fnWaitForPluginReady = jindo.$Fn(this._waitForPluginReady, this).bind(); // Register the core as a plugin so it can receive messages this.registerPlugin(this); }, setDebugger: function(oDebugger) { this.htOptions.oDebugger = oDebugger; oDebugger.setApp(this); }, exec : function(msg, args, oEvent){ // If the application is not yet ready just queue the message if(this.appStatus == nhn.husky.APP_STATUS.NOT_READY){ this.messageQueue[this.messageQueue.length] = {'msg':msg, 'args':args, 'event':oEvent}; return true; } this.exec = this._exec; this.exec(msg, args, oEvent); }, delayedExec : function(msg, args, nDelay, oEvent){ var fExec = jindo.$Fn(this.exec, this).bind(msg, args, oEvent); setTimeout(fExec, nDelay); }, _exec : function(msg, args, oEvent){ return (this._exec = this.htOptions.oDebugger?this._execWithDebugger:this._execWithoutDebugger).call(this, msg, args, oEvent); }, _execWithDebugger : function(msg, args, oEvent){ this.htOptions.oDebugger.log_MessageStart(msg, args); var bResult = this._doExec(msg, args, oEvent); this.htOptions.oDebugger.log_MessageEnd(msg, args); return bResult; }, _execWithoutDebugger : function(msg, args, oEvent){ return this._doExec(msg, args, oEvent); }, _doExec : function(msg, args, oEvent){ var bContinue = false; // Lazy메시지가 있으면 파일을 로딩한다. if(this.oLazyMessage[msg]){ var htLazyInfo = this.oLazyMessage[msg]; this._loadLazyFiles(msg, args, oEvent, htLazyInfo.aFilenames, 0); return false; } if(!this.oDisabledMessage[msg]){ var allArgs = []; if(args && args.length){ var iLen = args.length; for(var i=0; i= 0 && nhn.DOMFix.parentNode(aAllNodes[iChildIdx]) == aAllNodes[iCurIdx]){ iChildIdx = this._recurConstructClonedTree(aAllNodes, iChildIdx, oCurNodeCloneWithChildren); } // this may trigger an error message in IE when an erroneous script is inserted oClonedParentNode.insertBefore(oCurNodeCloneWithChildren, oClonedParentNode.firstChild); return iChildIdx; }; this._recurConstructClonedTree = _recurConstructClonedTree; aNodes[aNodes.length] = nhn.DOMFix.parentNode(aNodes[aNodes.length-1]); this._recurConstructClonedTree(aNodes, aNodes.length-1, oClonedParentNode); return {oStartContainer: oClonedStartContainer, oEndContainer: oClonedEndContainer}; }, cloneRange : function(){ return this._copyRange(new nhn.W3CDOMRange(this._window)); }, _copyRange : function(oClonedRange){ oClonedRange.collapsed = this.collapsed; oClonedRange.commonAncestorContainer = this.commonAncestorContainer; oClonedRange.endContainer = this.endContainer; oClonedRange.endOffset = this.endOffset; oClonedRange.startContainer = this.startContainer; oClonedRange.startOffset = this.startOffset; oClonedRange._document = this._document; return oClonedRange; }, collapse : function(toStart){ if(toStart){ this.endContainer = this.startContainer; this.endOffset = this.startOffset; }else{ this.startContainer = this.endContainer; this.startOffset = this.endOffset; } this._updateRangeInfo(); }, compareBoundaryPoints : function(how, sourceRange){ switch(how){ case nhn.W3CDOMRange.START_TO_START: return this._compareEndPoint(this.startContainer, this.startOffset, sourceRange.startContainer, sourceRange.startOffset); case nhn.W3CDOMRange.START_TO_END: return this._compareEndPoint(this.endContainer, this.endOffset, sourceRange.startContainer, sourceRange.startOffset); case nhn.W3CDOMRange.END_TO_END: return this._compareEndPoint(this.endContainer, this.endOffset, sourceRange.endContainer, sourceRange.endOffset); case nhn.W3CDOMRange.END_TO_START: return this._compareEndPoint(this.startContainer, this.startOffset, sourceRange.endContainer, sourceRange.endOffset); } }, _findBody : function(oNode){ if(!oNode){return null;} while(oNode){ if(oNode.tagName == "BODY"){return oNode;} oNode = nhn.DOMFix.parentNode(oNode); } return null; }, _compareEndPoint : function(oContainerA, iOffsetA, oContainerB, iOffsetB){ return this.oBrowserSelection.compareEndPoints(oContainerA, iOffsetA, oContainerB, iOffsetB); var iIdxA, iIdxB; if(!oContainerA || this._findBody(oContainerA) != this._document.body){ oContainerA = this._document.body; iOffsetA = 0; } if(!oContainerB || this._findBody(oContainerB) != this._document.body){ oContainerB = this._document.body; iOffsetB = 0; } var compareIdx = function(iIdxA, iIdxB){ // iIdxX == -1 when the node is the commonAncestorNode // if iIdxA == -1 // -> [[...]]... // if iIdxB == -1 // -> ...[[...]] if(iIdxB == -1){iIdxB = iIdxA+1;} if(iIdxA < iIdxB){return -1;} if(iIdxA == iIdxB){return 0;} return 1; }; var oCommonAncestor = this._getCommonAncestorContainer(oContainerA, oContainerB); // ================================================================================================================================================ // Move up both containers so that both containers are direct child nodes of the common ancestor node. From there, just compare the offset // Add 0.5 for each contaienrs that has "moved up" since the actual node is wrapped by 1 or more parent nodes and therefore its position is somewhere between idx & idx+1 // NODE1

NODE2

NODE3
// The position of NODE2 in COMMON_ANCESTOR is somewhere between after NODE1(idx1) and before NODE3(idx2), so we let that be 1.5 // container node A in common ancestor container var oNodeA = oContainerA; var oTmpNode = null; if(oNodeA != oCommonAncestor){ while((oTmpNode = nhn.DOMFix.parentNode(oNodeA)) != oCommonAncestor){oNodeA = oTmpNode;} iIdxA = this._getPosIdx(oNodeA)+0.5; }else{ iIdxA = iOffsetA; } // container node B in common ancestor container var oNodeB = oContainerB; if(oNodeB != oCommonAncestor){ while((oTmpNode = nhn.DOMFix.parentNode(oNodeB)) != oCommonAncestor){oNodeB = oTmpNode;} iIdxB = this._getPosIdx(oNodeB)+0.5; }else{ iIdxB = iOffsetB; } return compareIdx(iIdxA, iIdxB); }, _getCommonAncestorContainer : function(oNode1, oNode2){ oNode1 = oNode1 || this.startContainer; oNode2 = oNode2 || this.endContainer; var oComparingNode = oNode2; while(oNode1){ while(oComparingNode){ if(oNode1 == oComparingNode){return oNode1;} oComparingNode = nhn.DOMFix.parentNode(oComparingNode); } oComparingNode = oNode2; oNode1 = nhn.DOMFix.parentNode(oNode1); } return this._document.body; }, deleteContents : function(){ if(this.collapsed){return;} this._splitTextEndNodesOfTheRange(); var aNodes = this._getNodesInRange(); if(aNodes.length < 1){return;} var oPrevNode = aNodes[0].previousSibling; while(oPrevNode && this._isBlankTextNode(oPrevNode)){oPrevNode = oPrevNode.previousSibling;} var oNewStartContainer, iNewOffset = -1; if(!oPrevNode){ oNewStartContainer = nhn.DOMFix.parentNode(aNodes[0]); iNewOffset = 0; } for(var i=0; iA]B // -> []B // without these lines, the result would yeild to // -> []B if(oNewStartContainer == oNode && iNewOffset === 0){ iNewOffset = this._getPosIdx(oNewStartContainer); oNewStartContainer = nhn.DOMFix.parentNode(oNode); } } } if(!oPrevNode){ this.setStart(oNewStartContainer, iNewOffset, true, true); }else{ if(oPrevNode.tagName == "BODY"){ this.setStartBefore(oPrevNode, true); }else{ this.setStartAfter(oPrevNode, true); } } this.collapse(true); }, extractContents : function(){ var oClonedContents = this.cloneContents(); this.deleteContents(); return oClonedContents; }, getInsertBeforeNodes : function(){ var oFirstNode = null; var oParentContainer; if(this.startContainer.nodeType == "3"){ oParentContainer = nhn.DOMFix.parentNode(this.startContainer); if(this.startContainer.nodeValue.length <= this.startOffset){ oFirstNode = this.startContainer.nextSibling; }else{ oFirstNode = this.startContainer.splitText(this.startOffset); } }else{ oParentContainer = this.startContainer; oFirstNode = nhn.DOMFix.childNodes(this.startContainer)[this.startOffset]; } if(!oFirstNode || !nhn.DOMFix.parentNode(oFirstNode)){oFirstNode = null;} return {elParent: oParentContainer, elBefore: oFirstNode}; }, insertNode : function(newNode){ var oInsertBefore = this.getInsertBeforeNodes(); oInsertBefore.elParent.insertBefore(newNode, oInsertBefore.elBefore); this.setStartBefore(newNode); }, selectNode : function(refNode){ this.reset(this._window); this.setStartBefore(refNode); this.setEndAfter(refNode); }, selectNodeContents : function(refNode){ this.reset(this._window); this.setStart(refNode, 0, true); this.setEnd(refNode, nhn.DOMFix.childNodes(refNode).length); }, _endsNodeValidation : function(oNode, iOffset){ if(!oNode || this._findBody(oNode) != this._document.body){throw new Error("INVALID_NODE_TYPE_ERR oNode is not part of current document");} if(oNode.nodeType == 3){ if(iOffset > oNode.nodeValue.length){iOffset = oNode.nodeValue.length;} }else{ if(iOffset > nhn.DOMFix.childNodes(oNode).length){iOffset = nhn.DOMFix.childNodes(oNode).length;} } return iOffset; }, setEnd : function(refNode, offset, bSafe, bNoUpdate){ if(!bSafe){offset = this._endsNodeValidation(refNode, offset);} this.endContainer = refNode; this.endOffset = offset; if(!bNoUpdate){ if(!this.startContainer || this._compareEndPoint(this.startContainer, this.startOffset, this.endContainer, this.endOffset) != -1){ this.collapse(false); }else{ this._updateRangeInfo(); } } }, setEndAfter : function(refNode, bNoUpdate){ if(!refNode){throw new Error("INVALID_NODE_TYPE_ERR in setEndAfter");} if(refNode.tagName == "BODY"){ this.setEnd(refNode, nhn.DOMFix.childNodes(refNode).length, true, bNoUpdate); return; } this.setEnd(nhn.DOMFix.parentNode(refNode), this._getPosIdx(refNode)+1, true, bNoUpdate); }, setEndBefore : function(refNode, bNoUpdate){ if(!refNode){throw new Error("INVALID_NODE_TYPE_ERR in setEndBefore");} if(refNode.tagName == "BODY"){ this.setEnd(refNode, 0, true, bNoUpdate); return; } this.setEnd(nhn.DOMFix.parentNode(refNode), this._getPosIdx(refNode), true, bNoUpdate); }, setStart : function(refNode, offset, bSafe, bNoUpdate){ if(!bSafe){offset = this._endsNodeValidation(refNode, offset);} this.startContainer = refNode; this.startOffset = offset; if(!bNoUpdate){ if(!this.endContainer || this._compareEndPoint(this.startContainer, this.startOffset, this.endContainer, this.endOffset) != -1){ this.collapse(true); }else{ this._updateRangeInfo(); } } }, setStartAfter : function(refNode, bNoUpdate){ if(!refNode){throw new Error("INVALID_NODE_TYPE_ERR in setStartAfter");} if(refNode.tagName == "BODY"){ this.setStart(refNode, nhn.DOMFix.childNodes(refNode).length, true, bNoUpdate); return; } this.setStart(nhn.DOMFix.parentNode(refNode), this._getPosIdx(refNode)+1, true, bNoUpdate); }, setStartBefore : function(refNode, bNoUpdate){ if(!refNode){throw new Error("INVALID_NODE_TYPE_ERR in setStartBefore");} if(refNode.tagName == "BODY"){ this.setStart(refNode, 0, true, bNoUpdate); return; } this.setStart(nhn.DOMFix.parentNode(refNode), this._getPosIdx(refNode), true, bNoUpdate); }, surroundContents : function(newParent){ newParent.appendChild(this.extractContents()); this.insertNode(newParent); this.selectNode(newParent); }, toString : function(){ var oTmpContainer = this._document.createElement("DIV"); oTmpContainer.appendChild(this.cloneContents()); return oTmpContainer.textContent || oTmpContainer.innerText || ""; }, // this.oBrowserSelection.getCommonAncestorContainer which uses browser's built-in API runs faster but may return an incorrect value. // Call this function to fix the problem. // // In IE, the built-in API would return an incorrect value when, // 1. commonAncestorContainer is not selectable // AND // 2. The selected area will look the same when its child node is selected // eg) // when

TEST

is selected, TEST will be returned as commonAncestorContainer fixCommonAncestorContainer : function(){ if(!jindo.$Agent().navigator().ie){ return; } this.commonAncestorContainer = this._getCommonAncestorContainer(); }, _isBlankTextNode : function(oNode){ if(oNode.nodeType == 3 && oNode.nodeValue == ""){return true;} return false; }, _isAllChildBlankText : function(elNode){ for(var i=0, nLen=elNode.childNodes.length; i // // // // // // // // // // // // , it's easier to compare the position of B and D rather than C and F because they are siblings // // If the range were collapsed, oActualEndNode will precede oActualStartNode by doing this oActualStartNode = this._getNextNode(this._getPrevNode(oActualStartNode)); oActualEndNode = this._getPrevNode(this._getNextNode(oActualEndNode)); if(oActualStartNode && oActualEndNode && oActualEndNode.tagName != "BODY" && (this._getNextNode(oActualEndNode) == oActualStartNode || (oActualEndNode == oActualStartNode && this._isBlankTextNode(oActualEndNode))) ){ bCollapsed = true; } } return bCollapsed; }, _splitTextEndNodesOfTheRange : function(){ var oEndPoints = this._splitTextEndNodes({oStartContainer: this.startContainer, iStartOffset: this.startOffset, oEndContainer: this.endContainer, iEndOffset: this.endOffset}); this.startContainer = oEndPoints.oStartContainer; this.startOffset = oEndPoints.iStartOffset; this.endContainer = oEndPoints.oEndContainer; this.endOffset = oEndPoints.iEndOffset; }, _splitTextEndNodes : function(oEndPoints){ oEndPoints = this._splitStartTextNode(oEndPoints); oEndPoints = this._splitEndTextNode(oEndPoints); return oEndPoints; }, _splitStartTextNode : function(oEndPoints){ var oStartContainer = oEndPoints.oStartContainer; var iStartOffset = oEndPoints.iStartOffset; var oEndContainer = oEndPoints.oEndContainer; var iEndOffset = oEndPoints.iEndOffset; if(!oStartContainer){return oEndPoints;} if(oStartContainer.nodeType != 3){return oEndPoints;} if(iStartOffset === 0){return oEndPoints;} if(oStartContainer.nodeValue.length <= iStartOffset){return oEndPoints;} var oLastPart = oStartContainer.splitText(iStartOffset); if(oStartContainer == oEndContainer){ iEndOffset -= iStartOffset; oEndContainer = oLastPart; } oStartContainer = oLastPart; iStartOffset = 0; return {oStartContainer: oStartContainer, iStartOffset: iStartOffset, oEndContainer: oEndContainer, iEndOffset: iEndOffset}; }, _splitEndTextNode : function(oEndPoints){ var oStartContainer = oEndPoints.oStartContainer; var iStartOffset = oEndPoints.iStartOffset; var oEndContainer = oEndPoints.oEndContainer; var iEndOffset = oEndPoints.iEndOffset; if(!oEndContainer){return oEndPoints;} if(oEndContainer.nodeType != 3){return oEndPoints;} if(iEndOffset >= oEndContainer.nodeValue.length){return oEndPoints;} if(iEndOffset === 0){return oEndPoints;} oEndContainer.splitText(iEndOffset); return {oStartContainer: oStartContainer, iStartOffset: iStartOffset, oEndContainer: oEndContainer, iEndOffset: iEndOffset}; }, _getNodesInRange : function(){ if(this.collapsed){return [];} var oStartNode = this._getActualStartNode(this.startContainer, this.startOffset); var oEndNode = this._getActualEndNode(this.endContainer, this.endOffset); return this._getNodesBetween(oStartNode, oEndNode); }, _getActualStartNode : function(oStartContainer, iStartOffset){ var oStartNode = oStartContainer; if(oStartContainer.nodeType == 3){ if(iStartOffset >= oStartContainer.nodeValue.length){ oStartNode = this._getNextNode(oStartContainer); if(oStartNode.tagName == "BODY"){oStartNode = null;} }else{ oStartNode = oStartContainer; } }else{ if(iStartOffset < nhn.DOMFix.childNodes(oStartContainer).length){ oStartNode = nhn.DOMFix.childNodes(oStartContainer)[iStartOffset]; }else{ oStartNode = this._getNextNode(oStartContainer); if(oStartNode.tagName == "BODY"){oStartNode = null;} } } return oStartNode; }, _getActualEndNode : function(oEndContainer, iEndOffset){ var oEndNode = oEndContainer; if(iEndOffset === 0){ oEndNode = this._getPrevNode(oEndContainer); if(oEndNode.tagName == "BODY"){oEndNode = null;} }else if(oEndContainer.nodeType == 3){ oEndNode = oEndContainer; }else{ oEndNode = nhn.DOMFix.childNodes(oEndContainer)[iEndOffset-1]; } return oEndNode; }, _getNextNode : function(oNode){ if(!oNode || oNode.tagName == "BODY"){return this._document.body;} if(oNode.nextSibling){return oNode.nextSibling;} return this._getNextNode(nhn.DOMFix.parentNode(oNode)); }, _getPrevNode : function(oNode){ if(!oNode || oNode.tagName == "BODY"){return this._document.body;} if(oNode.previousSibling){return oNode.previousSibling;} return this._getPrevNode(nhn.DOMFix.parentNode(oNode)); }, // includes partially selected // for
, _getNodesBetween(b, c) will yield to b, "a" and c _getNodesBetween : function(oStartNode, oEndNode){ var aNodesBetween = []; this._nNodesBetweenLen = 0; if(!oStartNode || !oEndNode){return aNodesBetween;} // IE may throw an exception on "oCurNode = oCurNode.nextSibling;" when oCurNode is 'invalid', not null or undefined but somehow 'invalid'. // It happened during browser's build-in UNDO with control range selected(table). try{ this._recurGetNextNodesUntil(oStartNode, oEndNode, aNodesBetween); }catch(e){ return []; } return aNodesBetween; }, _recurGetNextNodesUntil : function(oNode, oEndNode, aNodesBetween){ if(!oNode){return false;} if(!this._recurGetChildNodesUntil(oNode, oEndNode, aNodesBetween)){return false;} var oNextToChk = oNode.nextSibling; while(!oNextToChk){ if(!(oNode = nhn.DOMFix.parentNode(oNode))){return false;} aNodesBetween[this._nNodesBetweenLen++] = oNode; if(oNode == oEndNode){return false;} oNextToChk = oNode.nextSibling; } return this._recurGetNextNodesUntil(oNextToChk, oEndNode, aNodesBetween); }, _recurGetChildNodesUntil : function(oNode, oEndNode, aNodesBetween){ if(!oNode){return false;} var bEndFound = false; var oCurNode = oNode; if(oCurNode.firstChild){ oCurNode = oCurNode.firstChild; while(oCurNode){ if(!this._recurGetChildNodesUntil(oCurNode, oEndNode, aNodesBetween)){ bEndFound = true; break; } oCurNode = oCurNode.nextSibling; } } aNodesBetween[this._nNodesBetweenLen++] = oNode; if(bEndFound){return false;} if(oNode == oEndNode){return false;} return true; } }); nhn.W3CDOMRange.START_TO_START = 0; nhn.W3CDOMRange.START_TO_END = 1; nhn.W3CDOMRange.END_TO_END = 2; nhn.W3CDOMRange.END_TO_START = 3; /** * @fileOverview This file contains a cross-browser function that implements all of the W3C's DOM Range specification and some more * @name HuskyRange.js */ nhn.HuskyRange = jindo.$Class({ _rxCursorHolder : /^(?:\uFEFF|\u00A0|\u200B|
)$/i, _rxTextAlign : /text-align:[^"';]*;?/i, setWindow : function(win){ this.reset(win || window); }, $init : function(win){ this.HUSKY_BOOMARK_START_ID_PREFIX = "husky_bookmark_start_"; this.HUSKY_BOOMARK_END_ID_PREFIX = "husky_bookmark_end_"; this.sBlockElement = "P|DIV|LI|H[1-6]|PRE"; this.sBlockContainer = "BODY|TABLE|TH|TR|TD|UL|OL|BLOCKQUOTE|FORM"; this.rxBlockElement = new RegExp("^("+this.sBlockElement+")$"); this.rxBlockContainer = new RegExp("^("+this.sBlockContainer+")$"); this.rxLineBreaker = new RegExp("^("+this.sBlockElement+"|"+this.sBlockContainer+")$"); this.rxHasBlock = new RegExp("(?:<(?:"+this.sBlockElement+"|"+this.sBlockContainer+").*?>|style=[\"']?[^>]*?(?:display\s?:\s?block)[^>]*?[\"']?)", "gi"); this.setWindow(win); }, select : function(){ try{ this.oBrowserSelection.selectRange(this); }catch(e){} }, setFromSelection : function(iNum){ this.setRange(this.oBrowserSelection.getRangeAt(iNum), true); }, setRange : function(oW3CRange, bSafe){ this.reset(this._window); this.setStart(oW3CRange.startContainer, oW3CRange.startOffset, bSafe, true); this.setEnd(oW3CRange.endContainer, oW3CRange.endOffset, bSafe); }, setEndNodes : function(oSNode, oENode){ this.reset(this._window); this.setEndAfter(oENode, true); this.setStartBefore(oSNode); }, splitTextAtBothEnds : function(){ this._splitTextEndNodesOfTheRange(); }, getStartNode : function(){ if(this.collapsed){ if(this.startContainer.nodeType == 3){ if(this.startOffset === 0){return null;} if(this.startContainer.nodeValue.length <= this.startOffset){return null;} return this.startContainer; } return null; } if(this.startContainer.nodeType == 3){ if(this.startOffset >= this.startContainer.nodeValue.length){return this._getNextNode(this.startContainer);} return this.startContainer; }else{ if(this.startOffset >= nhn.DOMFix.childNodes(this.startContainer).length){return this._getNextNode(this.startContainer);} return nhn.DOMFix.childNodes(this.startContainer)[this.startOffset]; } }, getEndNode : function(){ if(this.collapsed){return this.getStartNode();} if(this.endContainer.nodeType == 3){ if(this.endOffset === 0){return this._getPrevNode(this.endContainer);} return this.endContainer; }else{ if(this.endOffset === 0){return this._getPrevNode(this.endContainer);} return nhn.DOMFix.childNodes(this.endContainer)[this.endOffset-1]; } }, getNodeAroundRange : function(bBefore, bStrict){ if(!this.collapsed){return this.getStartNode();} if(this.startContainer && this.startContainer.nodeType == 3){return this.startContainer;} //if(this.collapsed && this.startContainer && this.startContainer.nodeType == 3) return this.startContainer; //if(!this.collapsed || (this.startContainer && this.startContainer.nodeType == 3)) return this.getStartNode(); var oBeforeRange, oAfterRange, oResult; if(this.startOffset >= nhn.DOMFix.childNodes(this.startContainer).length){ oAfterRange = this._getNextNode(this.startContainer); }else{ oAfterRange = nhn.DOMFix.childNodes(this.startContainer)[this.startOffset]; } if(this.endOffset === 0){ oBeforeRange = this._getPrevNode(this.endContainer); }else{ oBeforeRange = nhn.DOMFix.childNodes(this.endContainer)[this.endOffset-1]; } if(bBefore){ oResult = oBeforeRange; if(!oResult && !bStrict){oResult = oAfterRange;} }else{ oResult = oAfterRange; if(!oResult && !bStrict){oResult = oBeforeRange;} } return oResult; }, _getXPath : function(elNode){ var sXPath = ""; while(elNode && elNode.nodeType == 1){ sXPath = "/" + elNode.tagName+"["+this._getPosIdx4XPath(elNode)+"]" + sXPath; elNode = nhn.DOMFix.parentNode(elNode); } return sXPath; }, _getPosIdx4XPath : function(refNode){ var idx = 0; for(var node = refNode.previousSibling; node; node = node.previousSibling){ if(node.tagName == refNode.tagName){idx++;} } return idx; }, // this was written specifically for XPath Bookmark and it may not perform correctly for general purposes _evaluateXPath : function(sXPath, oDoc){ sXPath = sXPath.substring(1, sXPath.length-1); var aXPath = sXPath.split(/\//); var elNode = oDoc.body; for(var i=2; i -1 && elContainer){ var aChildNodes = nhn.DOMFix.childNodes(elContainer); var elNode = null; var nIdx = nTextNodeIdx; var nOffsetLeft = nOffset; while((elNode = aChildNodes[nIdx]) && elNode.nodeType == 3 && elNode.nodeValue.length < nOffsetLeft){ nOffsetLeft -= elNode.nodeValue.length; nIdx++; } elContainer = nhn.DOMFix.childNodes(elContainer)[nIdx]; nOffset = nOffsetLeft; } if(!elContainer){ elContainer = this._document.body; nOffset = 0; } return {elContainer: elContainer, nOffset: nOffset}; }, // this was written specifically for XPath Bookmark and it may not perform correctly for general purposes getXPathBookmark : function(){ var nTextNodeIdx1 = -1; var htEndPt1 = {elContainer: this.startContainer, nOffset: this.startOffset}; var elNode1 = this.startContainer; if(elNode1.nodeType == 3){ htEndPt1 = this._getFixedStartTextNode(); nTextNodeIdx1 = this._getPosIdx(htEndPt1.elContainer); elNode1 = nhn.DOMFix.parentNode(elNode1); } var sXPathNode1 = this._getXPath(elNode1); var oBookmark1 = {sXPath:sXPathNode1, nTextNodeIdx:nTextNodeIdx1, nOffset: htEndPt1.nOffset}; if(this.collapsed){ var oBookmark2 = {sXPath:sXPathNode1, nTextNodeIdx:nTextNodeIdx1, nOffset: htEndPt1.nOffset}; }else{ var nTextNodeIdx2 = -1; var htEndPt2 = {elContainer: this.endContainer, nOffset: this.endOffset}; var elNode2 = this.endContainer; if(elNode2.nodeType == 3){ htEndPt2 = this._getFixedEndTextNode(); nTextNodeIdx2 = this._getPosIdx(htEndPt2.elContainer); elNode2 = nhn.DOMFix.parentNode(elNode2); } var sXPathNode2 = this._getXPath(elNode2); var oBookmark2 = {sXPath:sXPathNode2, nTextNodeIdx:nTextNodeIdx2, nOffset: htEndPt2.nOffset}; } return [oBookmark1, oBookmark2]; }, moveToXPathBookmark : function(aBookmark){ if(!aBookmark){return false;} var oBookmarkInfo1 = this._evaluateXPathBookmark(aBookmark[0]); var oBookmarkInfo2 = this._evaluateXPathBookmark(aBookmark[1]); if(!oBookmarkInfo1["elContainer"] || !oBookmarkInfo2["elContainer"]){return;} this.startContainer = oBookmarkInfo1["elContainer"]; this.startOffset = oBookmarkInfo1["nOffset"]; this.endContainer = oBookmarkInfo2["elContainer"]; this.endOffset = oBookmarkInfo2["nOffset"]; return true; }, _getFixedTextContainer : function(elNode, nOffset){ while(elNode && elNode.nodeType == 3 && elNode.previousSibling && elNode.previousSibling.nodeType == 3){ nOffset += elNode.previousSibling.nodeValue.length; elNode = elNode.previousSibling; } return {elContainer:elNode, nOffset:nOffset}; }, _getFixedStartTextNode : function(){ return this._getFixedTextContainer(this.startContainer, this.startOffset); }, _getFixedEndTextNode : function(){ return this._getFixedTextContainer(this.endContainer, this.endOffset); }, placeStringBookmark : function(){ if(this.collapsed || jindo.$Agent().navigator().ie || jindo.$Agent().navigator().firefox){ return this.placeStringBookmark_NonWebkit(); }else{ return this.placeStringBookmark_Webkit(); } }, placeStringBookmark_NonWebkit : function(){ var sTmpId = (new Date()).getTime(); var oInsertionPoint = this.cloneRange(); oInsertionPoint.collapseToEnd(); var oEndMarker = this._document.createElement("SPAN"); oEndMarker.id = this.HUSKY_BOOMARK_END_ID_PREFIX+sTmpId; oInsertionPoint.insertNode(oEndMarker); var oInsertionPoint = this.cloneRange(); oInsertionPoint.collapseToStart(); var oStartMarker = this._document.createElement("SPAN"); oStartMarker.id = this.HUSKY_BOOMARK_START_ID_PREFIX+sTmpId; oInsertionPoint.insertNode(oStartMarker); // IE에서 빈 SPAN의 앞뒤로 커서가 이동하지 않아 문제가 발생 할 수 있어, 보이지 않는 특수 문자를 임시로 넣어 줌. if(jindo.$Agent().navigator().ie){ // SPAN의 위치가 TD와 TD 사이에 있을 경우, 텍스트 삽입 시 알수 없는 오류가 발생한다. // TD와 TD사이에서는 텍스트 삽입이 필요 없음으로 그냥 try/catch로 처리 try{ oStartMarker.innerHTML = unescape("%uFEFF"); }catch(e){} try{ oEndMarker.innerHTML = unescape("%uFEFF"); }catch(e){} } this.moveToBookmark(sTmpId); return sTmpId; }, placeStringBookmark_Webkit : function(){ var sTmpId = (new Date()).getTime(); var elInsertBefore, elInsertParent; // Do not insert the bookmarks between TDs as it will break the rendering in Chrome/Safari // -> modify the insertion position from [abc]abc to [abc]abc var oInsertionPoint = this.cloneRange(); oInsertionPoint.collapseToEnd(); elInsertBefore = this._document.createTextNode(""); oInsertionPoint.insertNode(elInsertBefore); elInsertParent = elInsertBefore.parentNode; if(elInsertBefore.previousSibling && elInsertBefore.previousSibling.tagName == "TD"){ elInsertParent = elInsertBefore.previousSibling; elInsertBefore = null; } var oEndMarker = this._document.createElement("SPAN"); oEndMarker.id = this.HUSKY_BOOMARK_END_ID_PREFIX+sTmpId; elInsertParent.insertBefore(oEndMarker, elInsertBefore); var oInsertionPoint = this.cloneRange(); oInsertionPoint.collapseToStart(); elInsertBefore = this._document.createTextNode(""); oInsertionPoint.insertNode(elInsertBefore); elInsertParent = elInsertBefore.parentNode; if(elInsertBefore.nextSibling && elInsertBefore.nextSibling.tagName == "TD"){ elInsertParent = elInsertBefore.nextSibling; elInsertBefore = elInsertParent.firstChild; } var oStartMarker = this._document.createElement("SPAN"); oStartMarker.id = this.HUSKY_BOOMARK_START_ID_PREFIX+sTmpId; elInsertParent.insertBefore(oStartMarker, elInsertBefore); //elInsertBefore.parentNode.removeChild(elInsertBefore); this.moveToBookmark(sTmpId); return sTmpId; }, cloneRange : function(){ return this._copyRange(new nhn.HuskyRange(this._window)); }, moveToBookmark : function(vBookmark){ if(typeof(vBookmark) != "object"){ return this.moveToStringBookmark(vBookmark); }else{ return this.moveToXPathBookmark(vBookmark); } }, getStringBookmark : function(sBookmarkID, bEndBookmark){ if(bEndBookmark){ return this._document.getElementById(this.HUSKY_BOOMARK_END_ID_PREFIX+sBookmarkID); }else{ return this._document.getElementById(this.HUSKY_BOOMARK_START_ID_PREFIX+sBookmarkID); } }, moveToStringBookmark : function(sBookmarkID, bIncludeBookmark){ var oStartMarker = this.getStringBookmark(sBookmarkID); var oEndMarker = this.getStringBookmark(sBookmarkID, true); if(!oStartMarker || !oEndMarker){return false;} this.reset(this._window); if(bIncludeBookmark){ this.setEndAfter(oEndMarker); this.setStartBefore(oStartMarker); }else{ this.setEndBefore(oEndMarker); this.setStartAfter(oStartMarker); } return true; }, removeStringBookmark : function(sBookmarkID){ /* var oStartMarker = this._document.getElementById(this.HUSKY_BOOMARK_START_ID_PREFIX+sBookmarkID); var oEndMarker = this._document.getElementById(this.HUSKY_BOOMARK_END_ID_PREFIX+sBookmarkID); if(oStartMarker) nhn.DOMFix.parentNode(oStartMarker).removeChild(oStartMarker); if(oEndMarker) nhn.DOMFix.parentNode(oEndMarker).removeChild(oEndMarker); */ this._removeAll(this.HUSKY_BOOMARK_START_ID_PREFIX+sBookmarkID); this._removeAll(this.HUSKY_BOOMARK_END_ID_PREFIX+sBookmarkID); }, _removeAll : function(sID){ var elNode; while((elNode = this._document.getElementById(sID))){ nhn.DOMFix.parentNode(elNode).removeChild(elNode); } }, collapseToStart : function(){ this.collapse(true); }, collapseToEnd : function(){ this.collapse(false); }, createAndInsertNode : function(sTagName){ var tmpNode = this._document.createElement(sTagName); this.insertNode(tmpNode); return tmpNode; }, getNodes : function(bSplitTextEndNodes, fnFilter){ if(bSplitTextEndNodes){this._splitTextEndNodesOfTheRange();} var aAllNodes = this._getNodesInRange(); var aFilteredNodes = []; if(!fnFilter){return aAllNodes;} for(var i=0; i= 0){return true;} if(bIncludePartlySelected){ if(startToEnd == 1){return false;} if(endToStart == -1){return false;} return true; } return false; }, isNodeInRange : function(oNode, bIncludePartlySelected, bContentOnly){ var oTmpRange = new nhn.HuskyRange(this._window); if(bContentOnly && oNode.firstChild){ oTmpRange.setStartBefore(oNode.firstChild); oTmpRange.setEndAfter(oNode.lastChild); }else{ oTmpRange.selectNode(oNode); } return this.isRangeInRange(oTmpRange, bIncludePartlySelected); }, pasteText : function(sText){ this.pasteHTML(sText.replace(/&/g, "&").replace(//g, ">").replace(/ /g, " ").replace(/"/g, """)); }, /** * TODO: 왜 clone 으로 조작할까? */ pasteHTML : function(sHTML){ var oTmpDiv = this._document.createElement("DIV"); oTmpDiv.innerHTML = sHTML; if(!oTmpDiv.firstChild){ this.deleteContents(); return; } // getLineInfo 전에 북마크를 삽입하지 않으면 IE에서 oLineBreaker가 P태그 바깥으로 잡히는 경우가 있음(P태그에 아무것도 없을때) var clone = this.cloneRange(); var sBM = clone.placeStringBookmark(); // [SMARTEDITORSUS-1960] PrivateTag, 템플릿삽입등 p태그안에 block 요소 삽입과 관련된 처리 // P태그인 경우, block요소가 들어오면 안된다. // 때문에 현재 위치의 컨테이너가 P태그이고 컨텐츠 내용이 block 요소인 경우 P태그를 쪼개고 그 사이에 컨텐츠를 div로 감싸서 넣도록 처리한다. var oLineInfo = clone.getLineInfo(), oStart = oLineInfo.oStart, oEnd = oLineInfo.oEnd; if(oStart.oLineBreaker && oStart.oLineBreaker.nodeName === "P" && clone.rxHasBlock.test(sHTML)){ // 선택영역을 조작해야 하므로 현재 선택된 요소들을 먼저 제거한다. clone.deleteContents(); var oParentNode = oStart.oLineBreaker.parentNode, oNextSibling = oStart.oLineBreaker.nextSibling; // 동일한 라인에 있으면 뒷부분은 쪼개서 다음 라인으로 삽입한다. if(oStart.oLineBreaker === oEnd.oLineBreaker){ var elBM = clone.getStringBookmark(sBM); clone.setEndNodes(elBM, oEnd.oLineBreaker); var oNextContents = clone.extractContents(); if(oNextSibling){ oParentNode.insertBefore(oNextContents, oNextSibling); }else{ oParentNode.appendChild(oNextContents); } oNextSibling = oStart.oLineBreaker.nextSibling; } // 선택영역 앞쪽이 속한 P태그에서 style과 align 정보를 복사한다. // 크롬의 경우 div의 style 에 text-align 이 있으면 align 속성은 무시되는데 // div 안의 block 요소는 text-align 의 대상이 아니라 정렬되지 않는 문제가 있기 때문에 // style 복사할 때 text-align 속성은 제외한다. oTmpDiv.style.cssText = oStart.oLineBreaker.style.cssText.replace(this._rxTextAlign, ''); // text-align 제외 oTmpDiv.align = oStart.oLineBreaker.align; // align 복사 // 컨텐츠 삽입 if(oNextSibling){ oParentNode.insertBefore(oTmpDiv, oNextSibling); }else{ oParentNode.appendChild(oTmpDiv); } // 컨텐츠 삽입 후에 북마크를 지운다. // 컨텐츠 삽입 전에 지우면 컨텐츠 삽입시 oNextSibling 가 북마크로 잡히는 경우 에러가 발생할 수 있음 clone.removeStringBookmark(sBM); // 컨텐츠 삽입 후 윗라인 P태그에 아무런 내용이 없으면 제거한다. this._removeEmptyP(this._getPrevElement(oTmpDiv)); // 아래 라인 P태그에 아무런 내용이 없는 경우는 그 다음 아래 라인이 있을때만 제거한다. // 아래 라인이 아예없으면 IE에서 커서가 들어가지 않기 때문에 라인을 추가해준다. var elNextLine = this._getNextElement(oTmpDiv); if(elNextLine){ var elAfterNext = this._getNextElement(elNextLine); if(elAfterNext){ this._removeEmptyP(elNextLine); elNextLine = elAfterNext; } }else{ // 아래 라인이 없으면 윗 라인 스타일을 복사하여 추가해준다. elNextLine = this._document.createElement("P"); elNextLine.style.cssText = oStart.oLineBreaker.style.cssText; elNextLine.align = oStart.oLineBreaker.align; elNextLine.innerHTML = '\uFEFF'; oParentNode.appendChild(elNextLine); } // 커서를 다음라인으로 위치시킨다. 그렇지 않으면 div태그와 p태그사이에 커서가 위치하게 된다. this.selectNodeContents(elNextLine); this.collapseToStart(); }else{ var oFirstNode = oTmpDiv.firstChild; var oLastNode = oTmpDiv.lastChild; this.collapseToStart(); while(oTmpDiv.lastChild){this.insertNode(oTmpDiv.lastChild);} this.setEndNodes(oFirstNode, oLastNode); // delete the content later as deleting it first may mass up the insertion point // eg)

[A]BCD

---paste O---> O

BCD

clone.moveToBookmark(sBM); clone.deleteContents(); clone.removeStringBookmark(sBM); } }, /** * 비어있는 P태그이면 제거한다. * @param {Element} el 검사할 Element */ _removeEmptyP : function(el){ if(el && el.nodeName === "P"){ var sInner = el.innerHTML; if(sInner === "" || this._rxCursorHolder.test(sInner)){ el.parentNode.removeChild(el); } } }, /** * 인접한 Element 노드를 찾는다. * @param {Node} oNode 기준 노드 * @param {Boolean} bPrev 앞뒤여부(true면 앞, false면 뒤) * @return {Element} 인접한 Element, 없으면 null 반환 */ _getSiblingElement : function(oNode, bPrev){ if(!oNode){ return null; } var oSibling = oNode[bPrev ? "previousSibling" : "nextSibling"]; if(oSibling && oSibling.nodeType === 1){ return oSibling; }else{ return arguments.callee(oSibling, bPrev); } }, /** * 앞쪽 인접한 Element 노드를 찾는다. * @param {Node} oNode 기준 노드 * @return {Element} 인접한 Element, 없으면 null 반환 */ _getPrevElement : function(oNode){ return this._getSiblingElement(oNode, true); }, /** * 뒤쪽 인접한 Element 노드를 찾는다. * @param {Node} oNode 기준 노드 * @return {Element} 인접한 Element, 없으면 null 반환 */ _getNextElement : function(oNode){ return this._getSiblingElement(oNode, false); }, toString : function(){ this.toString = nhn.W3CDOMRange.prototype.toString; return this.toString(); }, toHTMLString : function(){ var oTmpContainer = this._document.createElement("DIV"); oTmpContainer.appendChild(this.cloneContents()); return oTmpContainer.innerHTML; }, findAncestorByTagName : function(sTagName){ var oNode = this.commonAncestorContainer; while(oNode && oNode.tagName != sTagName){oNode = nhn.DOMFix.parentNode(oNode);} return oNode; }, selectNodeContents : function(oNode){ if(!oNode){return;} var oFirstNode = oNode.firstChild?oNode.firstChild:oNode; var oLastNode = oNode.lastChild?oNode.lastChild:oNode; this.reset(this._window); if(oFirstNode.nodeType == 3){ this.setStart(oFirstNode, 0, true); }else{ this.setStartBefore(oFirstNode); } if(oLastNode.nodeType == 3){ this.setEnd(oLastNode, oLastNode.nodeValue.length, true); }else{ this.setEndAfter(oLastNode); } }, /** * 노드의 취소선/밑줄 정보를 확인한다 * 관련 BTS [SMARTEDITORSUS-26] * @param {Node} oNode 취소선/밑줄을 확인할 노드 * @param {String} sValue textDecoration 정보 * @see nhn.HuskyRange#_checkTextDecoration */ _hasTextDecoration : function(oNode, sValue){ if(!oNode || !oNode.style){ return false; } if(oNode.style.textDecoration.indexOf(sValue) > -1){ return true; } if(sValue === "underline" && oNode.tagName === "U"){ return true; } if(sValue === "line-through" && (oNode.tagName === "S" || oNode.tagName === "STRIKE")){ return true; } return false; }, /** * 노드에 취소선/밑줄을 적용한다 * 관련 BTS [SMARTEDITORSUS-26] * [FF] 노드의 Style 에 textDecoration 을 추가한다 * [FF 외] U/STRIKE 태그를 추가한다 * @param {Node} oNode 취소선/밑줄을 적용할 노드 * @param {String} sValue textDecoration 정보 * @see nhn.HuskyRange#_checkTextDecoration */ _setTextDecoration : function(oNode, sValue){ if (jindo.$Agent().navigator().firefox) { // FF oNode.style.textDecoration = (oNode.style.textDecoration) ? oNode.style.textDecoration + " " + sValue : sValue; } else{ if(sValue === "underline"){ oNode.innerHTML = "" + oNode.innerHTML + "" }else if(sValue === "line-through"){ oNode.innerHTML = "" + oNode.innerHTML + "" } } }, /** * 인자로 전달받은 노드 상위의 취소선/밑줄 정보를 확인하여 노드에 적용한다 * 관련 BTS [SMARTEDITORSUS-26] * @param {Node} oNode 취소선/밑줄을 적용할 노드 */ _checkTextDecoration : function(oNode){ if(oNode.tagName !== "SPAN"){ return; } var bUnderline = false, bLineThrough = false, sTextDecoration = "", oParentNode = null; oChildNode = oNode.firstChild; /* check child */ while(oChildNode){ if(oChildNode.nodeType === 1){ bUnderline = (bUnderline || oChildNode.tagName === "U"); bLineThrough = (bLineThrough || oChildNode.tagName === "S" || oChildNode.tagName === "STRIKE"); } if(bUnderline && bLineThrough){ return; } oChildNode = oChildNode.nextSibling; } oParentNode = nhn.DOMFix.parentNode(oNode); /* check parent */ while(oParentNode && oParentNode.tagName !== "BODY"){ if(oParentNode.nodeType !== 1){ oParentNode = nhn.DOMFix.parentNode(oParentNode); continue; } if(!bUnderline && this._hasTextDecoration(oParentNode, "underline")){ bUnderline = true; this._setTextDecoration(oNode, "underline"); // set underline } if(!bLineThrough && this._hasTextDecoration(oParentNode, "line-through")){ bLineThrough = true; this._setTextDecoration(oNode, "line-through"); // set line-through } if(bUnderline && bLineThrough){ return; } oParentNode = nhn.DOMFix.parentNode(oParentNode); } }, /** * Range에 속한 노드들에 스타일을 적용한다 * @param {Object} oStyle 적용할 스타일을 가지는 Object (예) 글꼴 색 적용의 경우 { color : "#0075c8" } * @param {Object} [oAttribute] 적용할 속성을 가지는 Object (예) 맞춤범 검사의 경우 { _sm2_spchk: "강남콩", class: "se2_check_spell" } * @param {String} [sNewSpanMarker] 새로 추가된 SPAN 노드를 나중에 따로 처리해야하는 경우 마킹을 위해 사용하는 문자열 * @param {Boolean} [bIncludeLI] LI 도 스타일 적용에 포함할 것인지의 여부 [COM-1051] _getStyleParentNodes 메서드 참고하기 * @param {Boolean} [bCheckTextDecoration] 취소선/밑줄 처리를 적용할 것인지 여부 [SMARTEDITORSUS-26] _setTextDecoration 메서드 참고하기 */ styleRange : function(oStyle, oAttribute, sNewSpanMarker, bIncludeLI, bCheckTextDecoration){ var aStyleParents = this.aStyleParents = this._getStyleParentNodes(sNewSpanMarker, bIncludeLI); if(aStyleParents.length < 1){return;} var sName, sValue; for(var i=0; i 1){ // 싱글노드가 아니면 더이상 찾지 않고 null 반환 return null; } } if(oNode.nodeName === "SPAN"){ return oNode; }else{ return this._findParentSingleSpan(oNode.parentNode); } }, /** * 컨테이너 엘리먼트(elContainer)의 모든 자식노드가 노드 배열(waAllNodes)에 속하는지 확인한다 * 첫 번째 자식 노드와 마지막 자식 노드가 노드 배열에 속하는지를 확인한다 * @param {Element} elContainer 컨테이너 엘리먼트 * @param {jindo.$A} waAllNodes Node 의 $A 배열 * @param {Node} [oNode] 성능을 위한 옵션 노드로 컨테이너의 첫 번째 혹은 마지막 자식 노드와 같으면 indexOf 함수 사용을 줄일 수 있음 * @return {Array} Style 을 적용할 노드 배열 */ // check if all the child nodes of elContainer are in waAllNodes _isFullyContained : function(elContainer, waAllNodes, oNode){ var nSIdx, nEIdx; var oTmpNode = this._getVeryFirstRealChild(elContainer); // do quick checks before trying indexOf() because indexOf() function is very slow // oNode is optional if(oNode && oTmpNode == oNode){ nSIdx = 1; }else{ nSIdx = waAllNodes.indexOf(oTmpNode); } if(nSIdx != -1){ oTmpNode = this._getVeryLastRealChild(elContainer); if(oNode && oTmpNode == oNode){ nEIdx = 1; }else{ nEIdx = waAllNodes.indexOf(oTmpNode); } } return (nSIdx != -1 && nEIdx != -1); }, _getVeryFirstChild : function(oNode){ if(oNode.firstChild){return this._getVeryFirstChild(oNode.firstChild);} return oNode; }, _getVeryLastChild : function(oNode){ if(oNode.lastChild){return this._getVeryLastChild(oNode.lastChild);} return oNode; }, _getFirstRealChild : function(oNode){ var oFirstNode = oNode.firstChild; while(oFirstNode && oFirstNode.nodeType == 3 && oFirstNode.nodeValue == ""){oFirstNode = oFirstNode.nextSibling;} return oFirstNode; }, _getLastRealChild : function(oNode){ var oLastNode = oNode.lastChild; while(oLastNode && oLastNode.nodeType == 3 && oLastNode.nodeValue == ""){oLastNode = oLastNode.previousSibling;} return oLastNode; }, _getVeryFirstRealChild : function(oNode){ var oFirstNode = this._getFirstRealChild(oNode); if(oFirstNode){return this._getVeryFirstRealChild(oFirstNode);} return oNode; }, _getVeryLastRealChild : function(oNode){ var oLastNode = this._getLastRealChild(oNode); if(oLastNode){return this._getVeryLastChild(oLastNode);} return oNode; }, _getLineStartInfo : function(node){ var frontEndFinal = null; var frontEnd = node; var lineBreaker = node; var bParentBreak = false; var rxLineBreaker = this.rxLineBreaker; // vertical(parent) search function getLineStart(node){ if(!node){return;} if(frontEndFinal){return;} if(rxLineBreaker.test(node.tagName)){ lineBreaker = node; frontEndFinal = frontEnd; bParentBreak = true; return; }else{ frontEnd = node; } getFrontEnd(node.previousSibling); if(frontEndFinal){return;} getLineStart(nhn.DOMFix.parentNode(node)); } // horizontal(sibling) search function getFrontEnd(node){ if(!node){return;} if(frontEndFinal){return;} if(rxLineBreaker.test(node.tagName)){ lineBreaker = node; frontEndFinal = frontEnd; bParentBreak = false; return; } if(node.firstChild && node.tagName != "TABLE"){ var curNode = node.lastChild; while(curNode && !frontEndFinal){ getFrontEnd(curNode); curNode = curNode.previousSibling; } }else{ frontEnd = node; } if(!frontEndFinal){ getFrontEnd(node.previousSibling); } } if(rxLineBreaker.test(node.tagName)){ frontEndFinal = node; }else{ getLineStart(node); } return {oNode: frontEndFinal, oLineBreaker: lineBreaker, bParentBreak: bParentBreak}; }, _getLineEndInfo : function(node){ var backEndFinal = null; var backEnd = node; var lineBreaker = node; var bParentBreak = false; var rxLineBreaker = this.rxLineBreaker; // vertical(parent) search function getLineEnd(node){ if(!node){return;} if(backEndFinal){return;} if(rxLineBreaker.test(node.tagName)){ lineBreaker = node; backEndFinal = backEnd; bParentBreak = true; return; }else{ backEnd = node; } getBackEnd(node.nextSibling); if(backEndFinal){return;} getLineEnd(nhn.DOMFix.parentNode(node)); } // horizontal(sibling) search function getBackEnd(node){ if(!node){return;} if(backEndFinal){return;} if(rxLineBreaker.test(node.tagName)){ lineBreaker = node; backEndFinal = backEnd; bParentBreak = false; return; } if(node.firstChild && node.tagName != "TABLE"){ var curNode = node.firstChild; while(curNode && !backEndFinal){ getBackEnd(curNode); curNode = curNode.nextSibling; } }else{ backEnd = node; } if(!backEndFinal){ getBackEnd(node.nextSibling); } } if(rxLineBreaker.test(node.tagName)){ backEndFinal = node; }else{ getLineEnd(node); } return {oNode: backEndFinal, oLineBreaker: lineBreaker, bParentBreak: bParentBreak}; }, getLineInfo : function(bAfter){ var bAfter = bAfter || false; var oSNode = this.getStartNode(); var oENode = this.getEndNode(); // oSNode && oENode will be null if the range is currently collapsed and the cursor is not located in the middle of a text node. if(!oSNode){oSNode = this.getNodeAroundRange(!bAfter, true);} if(!oENode){oENode = this.getNodeAroundRange(!bAfter, true);} var oStart = this._getLineStartInfo(oSNode); var oStartNode = oStart.oNode; var oEnd = this._getLineEndInfo(oENode); var oEndNode = oEnd.oNode; if(oSNode != oStartNode || oENode != oEndNode){ // check if the start node is positioned after the range's ending point // or // if the end node is positioned before the range's starting point var iRelativeStartPos = this._compareEndPoint(nhn.DOMFix.parentNode(oStartNode), this._getPosIdx(oStartNode), this.endContainer, this.endOffset); var iRelativeEndPos = this._compareEndPoint(nhn.DOMFix.parentNode(oEndNode), this._getPosIdx(oEndNode)+1, this.startContainer, this.startOffset); if(!(iRelativeStartPos <= 0 && iRelativeEndPos >= 0)){ oSNode = this.getNodeAroundRange(false, true); oENode = this.getNodeAroundRange(false, true); oStart = this._getLineStartInfo(oSNode); oEnd = this._getLineEndInfo(oENode); } } return {oStart: oStart, oEnd: oEnd}; }, /** * 커서홀더나 공백을 제외한 child 노드가 하나만 있는 경우만 node 를 반환한다. * @param {Node} oNode 확인할 노드 * @return {Node} single child node를 반환한다. 없거나 두개 이상이면 null 을 반환 */ _findSingleChild : function(oNode){ if(!oNode){ return null; } var oSingleChild = null; // ZWNBSP 문자가 같이 있는 경우도 있기 때문에 실제 노드를 카운팅해야 함 for(var i = 0, nCnt = 0, sValue, oChild, aChildNodes = oNode.childNodes; (oChild = aChildNodes[i]); i++){ sValue = oChild.nodeValue; if(this._rxCursorHolder.test(sValue)){ continue; }else{ oSingleChild = oChild; nCnt++; } if(nCnt > 1){ // 싱글노드가 아니면 더이상 찾지 않고 null 반환 return null; } } return oSingleChild; }, /** * 해당요소의 최하위까지 검색해 커서홀더만 감싸고 있는지 여부를 반환 * @param {Node} oNode 확인할 노드 * @return {Boolean} 커서홀더만 있는 경우 true 반환 */ _hasCursorHolderOnly : function(oNode){ if(!oNode || oNode.nodeType !== 1){ return false; } if(this._rxCursorHolder.test(oNode.innerHTML)){ return true; }else{ return this._hasCursorHolderOnly(this._findSingleChild(oNode)); } } }).extend(nhn.W3CDOMRange); /** * @fileOverview This file contains cross-browser selection function * @name BrowserSelection.js */ nhn.BrowserSelection = function(win){ this.init = function(win){ this._window = win || window; this._document = this._window.document; }; this.init(win); // [SMARTEDITORSUS-888] IE9 이후로 document.createRange 를 지원 /* var oAgentInfo = jindo.$Agent().navigator(); if(oAgentInfo.ie){ nhn.BrowserSelectionImpl_IE.apply(this); }else{ nhn.BrowserSelectionImpl_FF.apply(this); }*/ if(!!this._document.createRange){ nhn.BrowserSelectionImpl_FF.apply(this); }else{ nhn.BrowserSelectionImpl_IE.apply(this); } this.selectRange = function(oRng){ this.selectNone(); this.addRange(oRng); }; this.selectionLoaded = true; if(!this._oSelection){this.selectionLoaded = false;} }; nhn.BrowserSelectionImpl_FF = function(){ this._oSelection = this._window.getSelection(); this.getRangeAt = function(iNum){ iNum = iNum || 0; try{ var oFFRange = this._oSelection.getRangeAt(iNum); }catch(e){return new nhn.W3CDOMRange(this._window);} return this._FFRange2W3CRange(oFFRange); }; this.addRange = function(oW3CRange){ var oFFRange = this._W3CRange2FFRange(oW3CRange); this._oSelection.addRange(oFFRange); }; this.selectNone = function(){ this._oSelection.removeAllRanges(); }; this.getCommonAncestorContainer = function(oW3CRange){ var oFFRange = this._W3CRange2FFRange(oW3CRange); return oFFRange.commonAncestorContainer; }; this.isCollapsed = function(oW3CRange){ var oFFRange = this._W3CRange2FFRange(oW3CRange); return oFFRange.collapsed; }; this.compareEndPoints = function(elContainerA, nOffsetA, elContainerB, nOffsetB){ var oFFRangeA = this._document.createRange(); var oFFRangeB = this._document.createRange(); oFFRangeA.setStart(elContainerA, nOffsetA); oFFRangeB.setStart(elContainerB, nOffsetB); oFFRangeA.collapse(true); oFFRangeB.collapse(true); try{ return oFFRangeA.compareBoundaryPoints(1, oFFRangeB); }catch(e){ return 1; } }; this._FFRange2W3CRange = function(oFFRange){ var oW3CRange = new nhn.W3CDOMRange(this._window); oW3CRange.setStart(oFFRange.startContainer, oFFRange.startOffset, true); oW3CRange.setEnd(oFFRange.endContainer, oFFRange.endOffset, true); return oW3CRange; }; this._W3CRange2FFRange = function(oW3CRange){ var oFFRange = this._document.createRange(); oFFRange.setStart(oW3CRange.startContainer, oW3CRange.startOffset); oFFRange.setEnd(oW3CRange.endContainer, oW3CRange.endOffset); return oFFRange; }; }; nhn.BrowserSelectionImpl_IE = function(){ this._oSelection = this._document.selection; this.oLastRange = { oBrowserRange : null, elStartContainer : null, nStartOffset : -1, elEndContainer : null, nEndOffset : -1 }; this._updateLastRange = function(oBrowserRange, oW3CRange){ this.oLastRange.oBrowserRange = oBrowserRange; this.oLastRange.elStartContainer = oW3CRange.startContainer; this.oLastRange.nStartOffset = oW3CRange.startOffset; this.oLastRange.elEndContainer = oW3CRange.endContainer; this.oLastRange.nEndOffset = oW3CRange.endOffset; }; this.getRangeAt = function(iNum){ iNum = iNum || 0; var oW3CRange, oBrowserRange; if(this._oSelection.type == "Control"){ oW3CRange = new nhn.W3CDOMRange(this._window); var oSelectedNode = this._oSelection.createRange().item(iNum); // if the selction occurs in a different document, ignore if(!oSelectedNode || oSelectedNode.ownerDocument != this._document){return oW3CRange;} oW3CRange.selectNode(oSelectedNode); return oW3CRange; }else{ //oBrowserRange = this._oSelection.createRangeCollection().item(iNum); oBrowserRange = this._oSelection.createRange(); var oSelectedNode = oBrowserRange.parentElement(); // if the selction occurs in a different document, ignore if(!oSelectedNode || oSelectedNode.ownerDocument != this._document){ oW3CRange = new nhn.W3CDOMRange(this._window); return oW3CRange; } oW3CRange = this._IERange2W3CRange(oBrowserRange); return oW3CRange; } }; this.addRange = function(oW3CRange){ var oIERange = this._W3CRange2IERange(oW3CRange); oIERange.select(); }; this.selectNone = function(){ this._oSelection.empty(); }; this.getCommonAncestorContainer = function(oW3CRange){ return this._W3CRange2IERange(oW3CRange).parentElement(); }; this.isCollapsed = function(oW3CRange){ var oRange = this._W3CRange2IERange(oW3CRange); var oRange2 = oRange.duplicate(); oRange2.collapse(); return oRange.isEqual(oRange2); }; this.compareEndPoints = function(elContainerA, nOffsetA, elContainerB, nOffsetB){ var oIERangeA, oIERangeB; if(elContainerA === this.oLastRange.elStartContainer && nOffsetA === this.oLastRange.nStartOffset){ oIERangeA = this.oLastRange.oBrowserRange.duplicate(); oIERangeA.collapse(true); }else{ if(elContainerA === this.oLastRange.elEndContainer && nOffsetA === this.oLastRange.nEndOffset){ oIERangeA = this.oLastRange.oBrowserRange.duplicate(); oIERangeA.collapse(false); }else{ oIERangeA = this._getIERangeAt(elContainerA, nOffsetA); } } if(elContainerB === this.oLastRange.elStartContainer && nOffsetB === this.oLastRange.nStartOffset){ oIERangeB = this.oLastRange.oBrowserRange.duplicate(); oIERangeB.collapse(true); }else{ if(elContainerB === this.oLastRange.elEndContainer && nOffsetB === this.oLastRange.nEndOffset){ oIERangeB = this.oLastRange.oBrowserRange.duplicate(); oIERangeB.collapse(false); }else{ oIERangeB = this._getIERangeAt(elContainerB, nOffsetB); } } return oIERangeA.compareEndPoints("StartToStart", oIERangeB); }; this._W3CRange2IERange = function(oW3CRange){ if(this.oLastRange.elStartContainer === oW3CRange.startContainer && this.oLastRange.nStartOffset === oW3CRange.startOffset && this.oLastRange.elEndContainer === oW3CRange.endContainer && this.oLastRange.nEndOffset === oW3CRange.endOffset){ return this.oLastRange.oBrowserRange; } var oStartIERange = this._getIERangeAt(oW3CRange.startContainer, oW3CRange.startOffset); var oEndIERange = this._getIERangeAt(oW3CRange.endContainer, oW3CRange.endOffset); oStartIERange.setEndPoint("EndToEnd", oEndIERange); this._updateLastRange(oStartIERange, oW3CRange); return oStartIERange; }; this._getIERangeAt = function(oW3CContainer, iW3COffset){ var oIERange = this._document.body.createTextRange(); var oEndPointInfoForIERange = this._getSelectableNodeAndOffsetForIE(oW3CContainer, iW3COffset); var oSelectableNode = oEndPointInfoForIERange.oSelectableNodeForIE; var iIEOffset = oEndPointInfoForIERange.iOffsetForIE; oIERange.moveToElementText(oSelectableNode); oIERange.collapse(oEndPointInfoForIERange.bCollapseToStart); oIERange.moveStart("character", iIEOffset); return oIERange; }; this._getSelectableNodeAndOffsetForIE = function(oW3CContainer, iW3COffset){ // var oIERange = this._document.body.createTextRange(); var oNonTextNode = null; var aChildNodes = null; var iNumOfLeftNodesToCount = 0; if(oW3CContainer.nodeType == 3){ oNonTextNode = nhn.DOMFix.parentNode(oW3CContainer); aChildNodes = nhn.DOMFix.childNodes(oNonTextNode); iNumOfLeftNodesToCount = aChildNodes.length; }else{ oNonTextNode = oW3CContainer; aChildNodes = nhn.DOMFix.childNodes(oNonTextNode); //iNumOfLeftNodesToCount = iW3COffset; iNumOfLeftNodesToCount = (iW3COffset=0){break;} oPrevNonTextNode = aChildNodes[i]; } var pointRangeIdx = i; if(pointRangeIdx !== 0 && aChildNodes[pointRangeIdx-1].nodeType == 3){ var oRgTextStart = this._document.body.createTextRange(); var oCurTextNode = null; if(oPrevNonTextNode){ oRgTextStart.moveToElementText(oPrevNonTextNode); oRgTextStart.collapse(false); oCurTextNode = oPrevNonTextNode.nextSibling; }else{ oRgTextStart.moveToElementText(oContainer); oRgTextStart.collapse(true); oCurTextNode = oContainer.firstChild; } var oRgTextsUpToThePoint = oRgOrigPoint.duplicate(); oRgTextsUpToThePoint.setEndPoint("StartToStart", oRgTextStart); var textCount = oRgTextsUpToThePoint.text.replace(/[\r\n]/g,"").length; while(textCount > oCurTextNode.nodeValue.length && oCurTextNode.nextSibling){ textCount -= oCurTextNode.nodeValue.length; oCurTextNode = oCurTextNode.nextSibling; } // this will enforce IE to re-reference oCurTextNode var oTmp = oCurTextNode.nodeValue; if(bStartPt && oCurTextNode.nextSibling && oCurTextNode.nextSibling.nodeType == 3 && textCount == oCurTextNode.nodeValue.length){ textCount -= oCurTextNode.nodeValue.length; oCurTextNode = oCurTextNode.nextSibling; } oContainer = oCurTextNode; offset = textCount; }else{ oContainer = oRgOrigPoint.parentElement(); offset = pointRangeIdx; } return {"oContainer" : oContainer, "iOffset" : offset}; }; }; nhn.DOMFix = new (jindo.$Class({ $init : function(){ if(jindo.$Agent().navigator().ie || jindo.$Agent().navigator().opera){ this.childNodes = this._childNodes_Fix; this.parentNode = this._parentNode_Fix; }else{ this.childNodes = this._childNodes_Native; this.parentNode = this._parentNode_Native; } }, _parentNode_Native : function(elNode){ return elNode.parentNode; }, _parentNode_Fix : function(elNode){ if(!elNode){return elNode;} while(elNode.previousSibling){elNode = elNode.previousSibling;} return elNode.parentNode; }, _childNodes_Native : function(elNode){ return elNode.childNodes; }, _childNodes_Fix : function(elNode){ var aResult = null; var nCount = 0; if(elNode){ var aResult = []; elNode = elNode.firstChild; while(elNode){ aResult[nCount++] = elNode; elNode=elNode.nextSibling; } } return aResult; } }))(); /*[ * ADD_APP_PROPERTY * * 주요 오브젝트를 모든 플러그인에서 this.oApp를 통해서 직접 접근 가능 하도록 등록한다. * * sPropertyName string 등록명 * oProperty object 등록시킬 오브젝트 * ---------------------------------------------------------------------------]*/ /*[ * REGISTER_BROWSER_EVENT * * 특정 브라우저 이벤트가 발생 했을때 Husky 메시지를 발생 시킨다. * * obj HTMLElement 브라우저 이벤트를 발생 시킬 HTML 엘리먼트 * sEvent string 발생 대기 할 브라우저 이벤트 * sMsg string 발생 할 Husky 메시지 * aParams array 메시지에 넘길 파라미터 * nDelay number 브라우저 이벤트 발생 후 Husky 메시지 발생 사이에 딜레이를 주고 싶을 경우 설정. (1/1000초 단위) * ---------------------------------------------------------------------------]*/ /*[ * DISABLE_MESSAGE * * 특정 메시지를 코어에서 무시하고 라우팅 하지 않도록 비활성화 한다. * * sMsg string 비활성화 시킬 메시지 * ---------------------------------------------------------------------------]*/ /*[ * ENABLE_MESSAGE * * 무시하도록 설정된 메시지를 무시하지 않도록 활성화 한다. * * sMsg string 활성화 시킬 메시지 * ---------------------------------------------------------------------------]*/ /*[ * EXEC_ON_READY_FUNCTION * * oApp.run({fnOnAppReady:fnOnAppReady})와 같이 run 호출 시점에 지정된 함수가 있을 경우 이를 MSG_APP_READY 시점에 실행 시킨다. * 코어에서 자동으로 발생시키는 메시지로 직접 발생시키지는 않도록 한다. * * none * ---------------------------------------------------------------------------]*/ /** * @pluginDesc Husky Framework에서 자주 사용되는 메시지를 처리하는 플러그인 */ nhn.husky.CorePlugin = jindo.$Class({ name : "CorePlugin", // nStatus = 0(request not sent), 1(request sent), 2(response received) // sContents = response htLazyLoadRequest_plugins : {}, htLazyLoadRequest_allFiles : {}, htHTMLLoaded : {}, $AFTER_MSG_APP_READY : function(){ this.oApp.exec("EXEC_ON_READY_FUNCTION", []); }, $ON_ADD_APP_PROPERTY : function(sPropertyName, oProperty){ this.oApp[sPropertyName] = oProperty; }, $ON_REGISTER_BROWSER_EVENT : function(obj, sEvent, sMsg, aParams, nDelay){ this.oApp.registerBrowserEvent(obj, sEvent, sMsg, aParams, nDelay); }, $ON_DISABLE_MESSAGE : function(sMsg){ this.oApp.disableMessage(sMsg, true); }, $ON_ENABLE_MESSAGE : function(sMsg){ this.oApp.disableMessage(sMsg, false); }, $ON_LOAD_FULL_PLUGIN : function(aFilenames, sClassName, sMsgName, oThisRef, oArguments){ var oPluginRef = oThisRef.$this || oThisRef; // var nIdx = _nIdx||0; var sFilename = aFilenames[0]; if(!this.htLazyLoadRequest_plugins[sFilename]){ this.htLazyLoadRequest_plugins[sFilename] = {nStatus:1, sContents:""}; } if(this.htLazyLoadRequest_plugins[sFilename].nStatus === 2){ //this.oApp.delayedExec("MSG_FULL_PLUGIN_LOADED", [sFilename, sClassName, sMsgName, oThisRef, oArguments, false], 0); this.oApp.exec("MSG_FULL_PLUGIN_LOADED", [sFilename, sClassName, sMsgName, oThisRef, oArguments, false]); }else{ this._loadFullPlugin(aFilenames, sClassName, sMsgName, oThisRef, oArguments, 0); } }, _loadFullPlugin : function(aFilenames, sClassName, sMsgName, oThisRef, oArguments, nIdx){ jindo.LazyLoading.load(nhn.husky.SE2M_Configuration.LazyLoad.sJsBaseURI+"/"+aFilenames[nIdx], jindo.$Fn(function(aFilenames, sClassName, sMsgName, oThisRef, oArguments, nIdx){ var sCurFilename = aFilenames[nIdx]; // plugin filename var sFilename = aFilenames[0]; if(nIdx == aFilenames.length-1){ this.htLazyLoadRequest_plugins[sFilename].nStatus=2; this.oApp.exec("MSG_FULL_PLUGIN_LOADED", [aFilenames, sClassName, sMsgName, oThisRef, oArguments]); return; } //this.oApp.exec("LOAD_FULL_PLUGIN", [aFilenames, sClassName, sMsgName, oThisRef, oArguments, nIdx+1]); this._loadFullPlugin(aFilenames, sClassName, sMsgName, oThisRef, oArguments, nIdx+1); }, this).bind(aFilenames, sClassName, sMsgName, oThisRef, oArguments, nIdx), "utf-8" ); }, $ON_MSG_FULL_PLUGIN_LOADED : function(aFilenames, sClassName, sMsgName, oThisRef, oArguments, oRes){ // oThisRef.$this는 현재 로드되는 플러그인이 parent 인스턴스일 경우 존재 함. oThisRef.$this는 현재 플러그인(oThisRef)를 parent로 삼고 있는 인스턴스 // oThisRef에 $this 속성이 없다면 parent가 아닌 일반 인스턴스 // oPluginRef는 결과적으로 상속 관계가 있다면 자식 인스턴스를 아니라면 일반적인 인스턴스를 가짐 var oPluginRef = oThisRef.$this || oThisRef; var sFilename = aFilenames; // now the source code is loaded, remove the loader handlers for(var i=0, nLen=oThisRef._huskyFLT.length; ili>button"); } }, $LOCAL_BEFORE_FIRST : function(sMsg) { var aToolItems = jindo.$$(">ul>li[class*=" + this.sUIClassPrefix + "]>button", this.elTextTool); var nItemLength = aToolItems.length; this.elFirstToolbarItem = this.elFirstToolbarItem || aToolItems[0]; this.elLastToolbarItem = aToolItems[nItemLength-1]; this.oApp.registerBrowserEvent(this.toolbarArea, "keydown", "NAVIGATE_TOOLBAR", []); }, /** * @param {Element} oAppContainer * @param {Object} htOptions * @param {Array} htOptions.aDisabled 비활성화할 버튼명 배열 */ $init : function(oAppContainer, htOptions){ this._htOptions = htOptions || {}; this.htUIList = {}; this.htWrappedUIList = {}; this.aUICmdMap = {}; this._assignHTMLElements(oAppContainer); }, $ON_MSG_APP_READY : function(){ if(this.oApp.bMobile){ this.oApp.registerBrowserEvent(this.toolbarArea, "touchstart", "EVENT_TOOLBAR_TOUCHSTART"); }else{ this.oApp.registerBrowserEvent(this.toolbarArea, "mouseover", "EVENT_TOOLBAR_MOUSEOVER"); this.oApp.registerBrowserEvent(this.toolbarArea, "mouseout", "EVENT_TOOLBAR_MOUSEOUT"); } this.oApp.registerBrowserEvent(this.toolbarArea, "mousedown", "EVENT_TOOLBAR_MOUSEDOWN"); this.oApp.exec("ADD_APP_PROPERTY", ["getToolbarButtonByUIName", jindo.$Fn(this.getToolbarButtonByUIName, this).bind()]); //웹접근성 //이 단계에서 oAppContainer가 정의되지 않은 상태라서 this.toolbarArea변수값을 사용하지 못하고 아래와 같이 다시 정의하였음. var elTool = jindo.$$.getSingle(".se2_tool"); this.oApp.exec("REGISTER_HOTKEY", ["esc", "FOCUS_EDITING_AREA", [], elTool]); // [SMARTEDITORSUS-1679] 초기 disabled 처리가 필요한 버튼은 비활성화 if(this._htOptions.aDisabled){ this._htOptions._sDisabled = "," + this._htOptions.aDisabled.toString() + ","; // 버튼을 활성화할때 비교하기 위한 문자열구성 this.oApp.exec("DISABLE_UI", [this._htOptions.aDisabled]); } }, $ON_NAVIGATE_TOOLBAR : function(weEvent) { var TAB_KEY_CODE = 9; //이벤트가 발생한 엘리먼트가 마지막 아이템이고 TAB 키가 눌려졌다면 if ((weEvent.element == this.elLastToolbarItem) && (weEvent.key().keyCode == TAB_KEY_CODE) ) { if (weEvent.key().shift) { //do nothing } else { this.elFirstToolbarItem.focus(); weEvent.stopDefault(); } } //이벤트가 발생한 엘리먼트가 첫번째 아이템이고 TAB 키가 눌려졌다면 if (weEvent.element == this.elFirstToolbarItem && (weEvent.key().keyCode == TAB_KEY_CODE)) { if (weEvent.key().shift) { weEvent.stopDefault(); this.elLastToolbarItem.focus(); } } }, //포커스가 툴바에 있는 상태에서 단축키를 누르면 에디팅 영역으로 다시 포커스가 가도록 하는 함수. (웹접근성) $ON_FOCUS_EDITING_AREA : function() { this.oApp.exec("FOCUS"); }, $ON_TOGGLE_TOOLBAR_ACTIVE_LAYER : function(elLayer, elBtn, sOpenCmd, aOpenArgs, sCloseCmd, aCloseArgs){ this.oApp.exec("TOGGLE_ACTIVE_LAYER", [elLayer, "MSG_TOOLBAR_LAYER_SHOWN", [elLayer, elBtn, sOpenCmd, aOpenArgs], sCloseCmd, aCloseArgs]); }, $ON_MSG_TOOLBAR_LAYER_SHOWN : function(elLayer, elBtn, aOpenCmd, aOpenArgs){ this.oApp.exec("POSITION_TOOLBAR_LAYER", [elLayer, elBtn]); if(aOpenCmd){ this.oApp.exec(aOpenCmd, aOpenArgs); } }, $ON_SHOW_TOOLBAR_ACTIVE_LAYER : function(elLayer, sCmd, aArgs, elBtn){ this.oApp.exec("SHOW_ACTIVE_LAYER", [elLayer, sCmd, aArgs]); this.oApp.exec("POSITION_TOOLBAR_LAYER", [elLayer, elBtn]); }, $ON_ENABLE_UI : function(sUIName){ this._enableUI(sUIName); }, /** * [SMARTEDITORSUS-1679] 여러개의 버튼을 동시에 비활성화 할 수 있도록 수정 * @param {String|Array} vUIName 비활성화할 버튼명, 배열일 경우 여러개 동시 적용 */ $ON_DISABLE_UI : function(sUIName){ if(sUIName instanceof Array){ for(var i = 0, sName; (sName = sUIName[i]); i++){ this._disableUI(sName); } }else{ this._disableUI(sUIName); } }, $ON_SELECT_UI : function(sUIName){ var welUI = this.htWrappedUIList[sUIName]; if(!welUI){ return; } welUI.removeClass("hover"); welUI.addClass("active"); }, $ON_DESELECT_UI : function(sUIName){ var welUI = this.htWrappedUIList[sUIName]; if(!welUI){ return; } welUI.removeClass("active"); }, /** * [SMARTEDITORSUS-1646] 툴바버튼 선택상태를 토글링한다. * @param {String} sUIName 토글링할 툴바버튼 이름 */ $ON_TOGGLE_UI_SELECTED : function(sUIName){ var welUI = this.htWrappedUIList[sUIName]; if(!welUI){ return; } if(welUI.hasClass("active")){ welUI.removeClass("active"); }else{ welUI.removeClass("hover"); welUI.addClass("active"); } }, $ON_ENABLE_ALL_UI : function(htOptions){ if(this.nUIStatus === 1){ return; } var sUIName, className; htOptions = htOptions || {}; var waExceptions = jindo.$A(htOptions.aExceptions || []); for(sUIName in this.htUIList){ if(sUIName && !waExceptions.has(sUIName)){ this._enableUI(sUIName); } // if(sUIName) this.oApp.exec("ENABLE_UI", [sUIName]); } // jindo.$Element(this.toolbarArea).removeClass("off"); this.nUIStatus = 1; }, $ON_DISABLE_ALL_UI : function(htOptions){ if(this.nUIStatus === 2){ return; } var sUIName; htOptions = htOptions || {}; var waExceptions = jindo.$A(htOptions.aExceptions || []); var bLeavlActiveLayer = htOptions.bLeaveActiveLayer || false; if(!bLeavlActiveLayer){ this.oApp.exec("HIDE_ACTIVE_LAYER"); } for(sUIName in this.htUIList){ if(sUIName && !waExceptions.has(sUIName)){ this._disableUI(sUIName); } // if(sUIName) this.oApp.exec("DISABLE_UI", [sUIName]); } // jindo.$Element(this.toolbarArea).addClass("off"); this.nUIStatus = 2; }, $ON_MSG_STYLE_CHANGED : function(sAttributeName, attributeValue){ if(attributeValue === "@^"){ this.oApp.exec("SELECT_UI", [sAttributeName]); }else{ this.oApp.exec("DESELECT_UI", [sAttributeName]); } }, $ON_POSITION_TOOLBAR_LAYER : function(elLayer, htOption){ var nLayerLeft, nLayerRight, nToolbarLeft, nToolbarRight; elLayer = jindo.$(elLayer); htOption = htOption || {}; var elBtn = jindo.$(htOption.elBtn); var sAlign = htOption.sAlign; var nMargin = -1; if(!elLayer){ return; } if(elBtn && elBtn.tagName && elBtn.tagName == "BUTTON"){ elBtn.parentNode.appendChild(elLayer); } var welLayer = jindo.$Element(elLayer); if(sAlign != "right"){ elLayer.style.left = "0"; nLayerLeft = welLayer.offset().left; nLayerRight = nLayerLeft + elLayer.offsetWidth; nToolbarLeft = this.welToolbarArea.offset().left; nToolbarRight = nToolbarLeft + this.toolbarArea.offsetWidth; if(nLayerRight > nToolbarRight){ welLayer.css("left", (nToolbarRight-nLayerRight-nMargin)+"px"); } if(nLayerLeft < nToolbarLeft){ welLayer.css("left", (nToolbarLeft-nLayerLeft+nMargin)+"px"); } }else{ elLayer.style.right = "0"; nLayerLeft = welLayer.offset().left; nLayerRight = nLayerLeft + elLayer.offsetWidth; nToolbarLeft = this.welToolbarArea.offset().left; nToolbarRight = nToolbarLeft + this.toolbarArea.offsetWidth; if(nLayerRight > nToolbarRight){ welLayer.css("right", -1*(nToolbarRight-nLayerRight-nMargin)+"px"); } if(nLayerLeft < nToolbarLeft){ welLayer.css("right", -1*(nToolbarLeft-nLayerLeft+nMargin)+"px"); } } }, $ON_EVENT_TOOLBAR_MOUSEOVER : function(weEvent){ if(this.nUIStatus === 2){ return; } var aAffectedElements = this._getAffectedElements(weEvent.element); for(var i=0; i childNodes.length<=2) // -> In this case, do not close here(mousedown). The layer will be closed on "click". If we close the layer here, the click event will open it again because it toggles the visibility. while(elTmp){ if(elTmp.className && elTmp.className.match(/active/) && (elTmp.childNodes.length>2 || elTmp.parentNode.className.match(/se2_pair/))){ return; } elTmp = elTmp.parentNode; } this.oApp.exec("HIDE_ACTIVE_LAYER_IF_NOT_CHILD", [weEvent.element]); }, _enableUI : function(sUIName){ // [SMARTEDITORSUS-1679] 초기 disabled 설정된 버튼은 skip if(this._htOptions._sDisabled && this._htOptions._sDisabled.indexOf(","+sUIName+",") > -1){ return; } var i, nLen; this.nUIStatus = 0; var welUI = this.htWrappedUIList[sUIName]; var elUI = this.htUIList[sUIName]; if(!welUI){ return; } welUI.removeClass("off"); var aAllBtns = elUI.getElementsByTagName("BUTTON"); for(i=0, nLen=aAllBtns.length; i //