(function() { Trex.Tool.Indent.Helper = { findBlocksToIndentFromRange: function(range, processor, savedCaret) { var startCaret = savedCaret.getCaret(_TRUE); var endCaret = savedCaret.getCaret(_FALSE); if (processor.isCollapsed()) { // getStartNode, getStartOffset 호출하지 않으면 오류가 발생한다. range.getStartNode(); range.getStartOffset(); var node = this.findBlockToIndent(startCaret, processor); var isEmptyParagraph = (node.tagName == "P" && node.firstChild == startCaret && node.lastChild == endCaret); if (isEmptyParagraph) { processor.stuffNode(node); } // TODO : block을 create한 경우만 restoreInternal을 해야한다. tab press[P로 감싸여 있지 않은 텍스트에서 tab 누르기] 테스트케이스와 관련있음 savedCaret.restoreInternal(); return [ node ]; } else { var iterator = new goog.dom.TextRangeIterator(startCaret, 0, endCaret, 0); return this.findBlocksToIndentFromIterator(processor, iterator); } }, findBlocksToIndentFromIterator: function(processor, iterator) { var self = this; var allNodes = self.collectAllNodes(iterator); var leafNodes = self.selectLeafNodes(allNodes); var validLeafNodes = self.filterUnableToIndent(leafNodes); var blocksToIndent = validLeafNodes.map(function(node) { return self.findBlockToIndent(node, processor); }); blocksToIndent = blocksToIndent.compact().uniq(); return blocksToIndent; }, collectAllNodes: function (iterator) { var allNodes = []; goog.iter.forEach(iterator, function(node) { if (!allNodes.contains(node)) { allNodes.push(node); } }); return allNodes; }, selectLeafNodes: function (nodes) { var leafNodes = []; nodes.each(function(node) { if (node.childNodes.length == 0) { leafNodes.push(node); } }); return leafNodes; }, filterUnableToIndent: function (nodes) { var result = []; nodes.each(function(node) { // indent를 하지 못하는 TextNode를 걸러낸다. if ($tom.kindOf(node, "ul,ol,dl")) { $tom.removeListIfEmpty(node); } else if ($tom.kindOf(node.parentNode, "table") && $tom.isText(node)) { } else if ($tom.kindOf(node.parentNode, "thead,tbody,tfooter") && !$tom.kindOf(node, "tr")) { } else if ($tom.kindOf(node.parentNode, "tr") && !$tom.kindOf(node, "th,td")) { } else if ($tom.kindOf(node.parentNode, "ul,ol,dl") && !$tom.kindOf(node, "li,dd,dt")) { } else { result.push(node); } }); return result; }, findBlockToIndent: function(node) { var block = this.findOrCreateBlockForNode(node); return this.findIndentableHigherBlock(block); }, findOrCreateBlockForNode: function(node) { if ($tom.isText(node) || $tom.kindOf(node, "%inline,img")) { var blockAncestor = $tom.ancestor(node, "p,li,dd,dt,h1,h2,h3,h4,h5,h6,div"); if (blockAncestor && $tom.children(blockAncestor, "%block").length == 0) { return blockAncestor; } else { blockAncestor = $tom.ancestor(node, "%paragraph,pre,noscript,form,hr,address,fieldset,blockquote"); return $tom.wrapInlinesWithP(node, blockAncestor); } } else { return node; } }, findIndentableHigherBlock: function(block) { var foundNode = _NULL; var visitNode = block; while (visitNode && visitNode.tagName != "BODY") { if (!foundNode && $tom.kindOf(visitNode, "p,div,h1,h2,h3,h4,h5,h6")) { foundNode = visitNode; } else if ($tom.kindOf(visitNode, "li,dd,dt")) { return visitNode; } else if (foundNode && $tom.kindOf(visitNode, "td,th")) { return foundNode; } visitNode = visitNode.parentNode; } return foundNode; }, findAncestorTableCell: function(node) { return $tom.ancestor(node, "td,th"); }, findNextCell: function(node) { var currentCell = this.findCurrentCell(node); var nextCell = $tom.next(currentCell, "td,th"); if (!nextCell) { var nextRow = $tom.next($tom.parent(currentCell), "tr"); if (nextRow) { nextCell = $tom.first(nextRow, "td,th"); } } return nextCell; }, findPreviousCell: function(node) { var currentCell = this.findCurrentCell(node); var prevCell = $tom.previous(currentCell, "td,th"); if (!prevCell) { var prevRow = $tom.previous($tom.parent(currentCell), "tr"); if (prevRow) { prevCell = $tom.last(prevRow, "td,th"); } } return prevCell; }, findCurrentCell: function(node) { return $tom.kindOf(node, "td,th") ? node : this.findAncestorTableCell(node); }, isCaretOnStartOf: function(node, range) { var startNode = range.getStartNode(); var startOffset = range.getStartOffset(); while ($tom.isElement(startNode) && startNode.childNodes.length > 0) { startNode = startNode.childNodes[startOffset]; startOffset = 0; } if (!startNode) { return _TRUE; } var iterator = new goog.dom.TextRangeIterator(node, 0, startNode, startOffset); var hasContent = _FALSE; goog.iter.forEach(iterator, function(visiting) { if (visiting.nodeType == 3 && !$tom.kindOf(visiting.parentNode, "script,style")) { var text = (visiting === startNode) ? visiting.nodeValue.substring(0, startOffset) : visiting.nodeValue; text = text.replace(Trex.__WORD_JOINER_REGEXP, ""); hasContent = $tom.removeMeaninglessSpace(text).length > 0; } else if ($tom.isElement(visiting)) { if ($tom.kindOf(visiting, "img,embed,iframe")) { hasContent = _TRUE; } } if (hasContent) { throw goog.iter.StopIteration; } }); return !hasContent; } }; var indentHelper = Trex.Tool.Indent.Helper; var $caret_moved = {}; // range 사용해서 indent할 block을 찾아서 chain handler 작업 지시한다. Trex.Tool.Indent.RangeIndenter = Trex.Class.create({ initialize: function(handler) { this.handler = handler; }, indent: function(processor) { var self = this; processor.executeUsingCaret(function(range, savedCaret) { var blockNodes = indentHelper.findBlocksToIndentFromRange(range, processor, savedCaret); blockNodes.each(function(node) { try { self.handler.handle(node, processor, range); } catch (e) { if (e == $caret_moved) { savedCaret.dispose(); } else { throw e; } } }); }); } }); Trex.Tool.Indent.TableCellIndenter = Trex.Class.create({ initialize: function(handler) { this.handler = handler; }, indent: function(processor) { var self = this; var tableCells = (processor.table) ? processor.table.getTdArr() : []; tableCells.each(function(cell) { var iterator = new goog.dom.TagIterator(cell); var blockNodes = indentHelper.findBlocksToIndentFromIterator(processor, iterator); blockNodes.each(function(node) { self.handler.handle(node, processor, _NULL); }); }); } }); Trex.Tool.Indent.Judge = { ChildOfFirstTableCell: function(node) { var tableCell = indentHelper.findAncestorTableCell(node); return tableCell && !indentHelper.findPreviousCell(tableCell); }, ChildOfLastTableCell: function(node) { var tableCell = indentHelper.findAncestorTableCell(node); return tableCell && !indentHelper.findNextCell(tableCell); }, ChildOfTableCell: function(node) { return indentHelper.findAncestorTableCell(node); }, ListItem: function(node) { return $tom.kindOf(node, "li") && $tom.kindOf(node.parentNode, "ol,ul"); }, OneDepthList: function(node) { if ($tom.kindOf(node, "li")) { // TODO: depth계산을 할 것이냐 부모/조상만 확인하여 return 할 것이냐 고민.. var listBuilder = new Trex.Tool.StyledList.ListBuilder(); if (listBuilder.countDepthOfList(node) == 1) { return _TRUE; } } return _FALSE; }, IndentedBlockNode: function(node) { return $tom.kindOf(node, "%block") && node.style && node.style.marginLeft != ""; }, BlockNode: function(node) { // TODO %block vs %paragraph return $tom.kindOf(node, "%block"); }, HeadOfParagraph: function(node, processor, range) { return indentHelper.isCaretOnStartOf(node, range); }, And: function(judge1, judge2){ return function() { return judge1.apply(this, arguments) && judge2.apply(this, arguments); } }, AlwaysTrue: function() { return _TRUE; } }; Trex.Tool.Indent.Operation = { /* indent */ GoToBelowTable: function(node, processor) { var table = $tom.ancestor(node, 'table'); processor.bookmarkToNext(table); throw $caret_moved; }, GoToNextCell: function(node, processor) { var nextCell = indentHelper.findNextCell(node); if (nextCell) { processor.selectFirstText(nextCell); throw $caret_moved; } }, IndentListItem: function(node) { var groupNode = $tom.ancestor(node, 'ul,ol,dl'); if (groupNode) { var prevSibling = $tom.previous(node); var nextSibling = $tom.next(node); if ($tom.kindOf(prevSibling, "ul,ol,dl")) { // move to previous Group $tom.append(prevSibling, node); } else { var newGroupNode = $tom.clone(groupNode); $tom.applyStyles(newGroupNode, { marginLeft: _NULL, paddingLeft: _NULL }); $tom.wrap(newGroupNode, node); } // move next Siblings to same parent if ($tom.kindOf(nextSibling, "ul,ol,dl")) { $tom.moveChild(nextSibling, node.parentNode); $tom.remove(nextSibling); } } }, getChildrenAsElement: function(node) { var blocks = []; var childNodes = node.childNodes; for (var i = 0, len = childNodes.length; i < len; i++) { var child = childNodes[i]; if ($tom.isText(child)) { var wrappedChild = $tom.wrapInlinesWithP(child, node); blocks.push(wrappedChild); } else if ($tom.isElement(child)) { blocks.push(child); } } return blocks; }, IndentBlockNode: function(node) { $tom.applyStyles(node, {marginLeft: "+2em"}); }, // AddFourSpaces: function(node, processor) { // processor.pasteContent("    ", _FALSE); // }, /* outdent */ GoToAboveTable: function(node, processor) { var table = $tom.ancestor(node, 'table'); processor.bookmarkToPrevious(table); throw $caret_moved; }, GoToPreviousCell: function(node, processor) { var previousCell = indentHelper.findPreviousCell(node); if (previousCell) { processor.moveCaretTo(previousCell, _TRUE); throw $caret_moved; } }, OutdentListItem: function(node, processor) { var list = $tom.ancestor(node, 'ul,ol,dl'); if (!list) { return; } var parentNode = list.parentNode; if ($tom.kindOf(parentNode, "li")) { $tom.unwrap(parentNode); parentNode = list.parentNode; } var grandParentList = $tom.kindOf(parentNode, 'ul,ol,dl') ? parentNode : _NULL; var newList; if (grandParentList) { newList = $tom.divideNode(list, $tom.indexOf(node)); $tom.insertAt(node, newList); } else { newList = $tom.divideNode(list, $tom.indexOf(node)); var cssText = $tom.getStyleText(node); // list의 스타일을 p에도 적용한다. var p = processor.newNode('p'); $tom.setStyleText(p, cssText); $tom.replace(node, p); $tom.insertAt(p, newList); } $tom.removeListIfEmpty(list); $tom.removeListIfEmpty(newList); }, OutdentBlockNode: function(node) { $tom.applyStyles(node, {marginLeft: "-2em"}); }, Propagate: function() { throw $propagate; } }; })();