indentHelper.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. (function() {
  2. Trex.Tool.Indent.Helper = {
  3. findBlocksToIndentFromRange: function(range, processor, savedCaret) {
  4. var startCaret = savedCaret.getCaret(_TRUE);
  5. var endCaret = savedCaret.getCaret(_FALSE);
  6. if (processor.isCollapsed()) {
  7. // getStartNode, getStartOffset 호출하지 않으면 오류가 발생한다.
  8. range.getStartNode();
  9. range.getStartOffset();
  10. var node = this.findBlockToIndent(startCaret, processor);
  11. var isEmptyParagraph = (node.tagName == "P" && node.firstChild == startCaret && node.lastChild == endCaret);
  12. if (isEmptyParagraph) {
  13. processor.stuffNode(node);
  14. }
  15. // TODO : block을 create한 경우만 restoreInternal을 해야한다. tab press[P로 감싸여 있지 않은 텍스트에서 tab 누르기] 테스트케이스와 관련있음
  16. savedCaret.restoreInternal();
  17. return [ node ];
  18. } else {
  19. var iterator = new goog.dom.TextRangeIterator(startCaret, 0, endCaret, 0);
  20. return this.findBlocksToIndentFromIterator(processor, iterator);
  21. }
  22. },
  23. findBlocksToIndentFromIterator: function(processor, iterator) {
  24. var self = this;
  25. var allNodes = self.collectAllNodes(iterator);
  26. var leafNodes = self.selectLeafNodes(allNodes);
  27. var validLeafNodes = self.filterUnableToIndent(leafNodes);
  28. var blocksToIndent = validLeafNodes.map(function(node) {
  29. return self.findBlockToIndent(node, processor);
  30. });
  31. blocksToIndent = blocksToIndent.compact().uniq();
  32. return blocksToIndent;
  33. },
  34. collectAllNodes: function (iterator) {
  35. var allNodes = [];
  36. goog.iter.forEach(iterator, function(node) {
  37. if (!allNodes.contains(node)) {
  38. allNodes.push(node);
  39. }
  40. });
  41. return allNodes;
  42. },
  43. selectLeafNodes: function (nodes) {
  44. var leafNodes = [];
  45. nodes.each(function(node) {
  46. if (node.childNodes.length == 0) {
  47. leafNodes.push(node);
  48. }
  49. });
  50. return leafNodes;
  51. },
  52. filterUnableToIndent: function (nodes) {
  53. var result = [];
  54. nodes.each(function(node) {
  55. // indent를 하지 못하는 TextNode를 걸러낸다.
  56. if ($tom.kindOf(node, "ul,ol,dl")) {
  57. $tom.removeListIfEmpty(node);
  58. } else if ($tom.kindOf(node.parentNode, "table") && $tom.isText(node)) {
  59. } else if ($tom.kindOf(node.parentNode, "thead,tbody,tfooter") && !$tom.kindOf(node, "tr")) {
  60. } else if ($tom.kindOf(node.parentNode, "tr") && !$tom.kindOf(node, "th,td")) {
  61. } else if ($tom.kindOf(node.parentNode, "ul,ol,dl") && !$tom.kindOf(node, "li,dd,dt")) {
  62. } else {
  63. result.push(node);
  64. }
  65. });
  66. return result;
  67. },
  68. findBlockToIndent: function(node) {
  69. var block = this.findOrCreateBlockForNode(node);
  70. return this.findIndentableHigherBlock(block);
  71. },
  72. findOrCreateBlockForNode: function(node) {
  73. if ($tom.isText(node) || $tom.kindOf(node, "%inline,img")) {
  74. var blockAncestor = $tom.ancestor(node, "p,li,dd,dt,h1,h2,h3,h4,h5,h6,div");
  75. if (blockAncestor && $tom.children(blockAncestor, "%block").length == 0) {
  76. return blockAncestor;
  77. } else {
  78. blockAncestor = $tom.ancestor(node, "%paragraph,pre,noscript,form,hr,address,fieldset,blockquote");
  79. return $tom.wrapInlinesWithP(node, blockAncestor);
  80. }
  81. } else {
  82. return node;
  83. }
  84. },
  85. findIndentableHigherBlock: function(block) {
  86. var foundNode = _NULL;
  87. var visitNode = block;
  88. while (visitNode && visitNode.tagName != "BODY") {
  89. if (!foundNode && $tom.kindOf(visitNode, "p,div,h1,h2,h3,h4,h5,h6")) {
  90. foundNode = visitNode;
  91. } else if ($tom.kindOf(visitNode, "li,dd,dt")) {
  92. return visitNode;
  93. } else if (foundNode && $tom.kindOf(visitNode, "td,th")) {
  94. return foundNode;
  95. }
  96. visitNode = visitNode.parentNode;
  97. }
  98. return foundNode;
  99. },
  100. findAncestorTableCell: function(node) {
  101. return $tom.ancestor(node, "td,th");
  102. },
  103. findNextCell: function(node) {
  104. var currentCell = this.findCurrentCell(node);
  105. var nextCell = $tom.next(currentCell, "td,th");
  106. if (!nextCell) {
  107. var nextRow = $tom.next($tom.parent(currentCell), "tr");
  108. if (nextRow) {
  109. nextCell = $tom.first(nextRow, "td,th");
  110. }
  111. }
  112. return nextCell;
  113. },
  114. findPreviousCell: function(node) {
  115. var currentCell = this.findCurrentCell(node);
  116. var prevCell = $tom.previous(currentCell, "td,th");
  117. if (!prevCell) {
  118. var prevRow = $tom.previous($tom.parent(currentCell), "tr");
  119. if (prevRow) {
  120. prevCell = $tom.last(prevRow, "td,th");
  121. }
  122. }
  123. return prevCell;
  124. },
  125. findCurrentCell: function(node) {
  126. return $tom.kindOf(node, "td,th") ? node : this.findAncestorTableCell(node);
  127. },
  128. isCaretOnStartOf: function(node, range) {
  129. var startNode = range.getStartNode();
  130. var startOffset = range.getStartOffset();
  131. while ($tom.isElement(startNode) && startNode.childNodes.length > 0) {
  132. startNode = startNode.childNodes[startOffset];
  133. startOffset = 0;
  134. }
  135. if (!startNode) {
  136. return _TRUE;
  137. }
  138. var iterator = new goog.dom.TextRangeIterator(node, 0, startNode, startOffset);
  139. var hasContent = _FALSE;
  140. goog.iter.forEach(iterator, function(visiting) {
  141. if (visiting.nodeType == 3 && !$tom.kindOf(visiting.parentNode, "script,style")) {
  142. var text = (visiting === startNode) ? visiting.nodeValue.substring(0, startOffset) : visiting.nodeValue;
  143. text = text.replace(Trex.__WORD_JOINER_REGEXP, "");
  144. hasContent = $tom.removeMeaninglessSpace(text).length > 0;
  145. } else if ($tom.isElement(visiting)) {
  146. if ($tom.kindOf(visiting, "img,embed,iframe")) {
  147. hasContent = _TRUE;
  148. }
  149. }
  150. if (hasContent) {
  151. throw goog.iter.StopIteration;
  152. }
  153. });
  154. return !hasContent;
  155. }
  156. };
  157. var indentHelper = Trex.Tool.Indent.Helper;
  158. var $caret_moved = {};
  159. // range 사용해서 indent할 block을 찾아서 chain handler 작업 지시한다.
  160. Trex.Tool.Indent.RangeIndenter = Trex.Class.create({
  161. initialize: function(handler) {
  162. this.handler = handler;
  163. },
  164. indent: function(processor) {
  165. var self = this;
  166. processor.executeUsingCaret(function(range, savedCaret) {
  167. var blockNodes = indentHelper.findBlocksToIndentFromRange(range, processor, savedCaret);
  168. blockNodes.each(function(node) {
  169. try {
  170. self.handler.handle(node, processor, range);
  171. } catch (e) {
  172. if (e == $caret_moved) {
  173. savedCaret.dispose();
  174. } else {
  175. throw e;
  176. }
  177. }
  178. });
  179. });
  180. }
  181. });
  182. Trex.Tool.Indent.TableCellIndenter = Trex.Class.create({
  183. initialize: function(handler) {
  184. this.handler = handler;
  185. },
  186. indent: function(processor) {
  187. var self = this;
  188. var tableCells = (processor.table) ? processor.table.getTdArr() : [];
  189. tableCells.each(function(cell) {
  190. var iterator = new goog.dom.TagIterator(cell);
  191. var blockNodes = indentHelper.findBlocksToIndentFromIterator(processor, iterator);
  192. blockNodes.each(function(node) {
  193. self.handler.handle(node, processor, _NULL);
  194. });
  195. });
  196. }
  197. });
  198. Trex.Tool.Indent.Judge = {
  199. ChildOfFirstTableCell: function(node) {
  200. var tableCell = indentHelper.findAncestorTableCell(node);
  201. return tableCell && !indentHelper.findPreviousCell(tableCell);
  202. },
  203. ChildOfLastTableCell: function(node) {
  204. var tableCell = indentHelper.findAncestorTableCell(node);
  205. return tableCell && !indentHelper.findNextCell(tableCell);
  206. },
  207. ChildOfTableCell: function(node) {
  208. return indentHelper.findAncestorTableCell(node);
  209. },
  210. ListItem: function(node) {
  211. return $tom.kindOf(node, "li") && $tom.kindOf(node.parentNode, "ol,ul");
  212. },
  213. OneDepthList: function(node) {
  214. if ($tom.kindOf(node, "li")) {
  215. // TODO: depth계산을 할 것이냐 부모/조상만 확인하여 return 할 것이냐 고민..
  216. var listBuilder = new Trex.Tool.StyledList.ListBuilder();
  217. if (listBuilder.countDepthOfList(node) == 1) {
  218. return _TRUE;
  219. }
  220. }
  221. return _FALSE;
  222. },
  223. IndentedBlockNode: function(node) {
  224. return $tom.kindOf(node, "%block") && node.style && node.style.marginLeft != "";
  225. },
  226. BlockNode: function(node) {
  227. // TODO %block vs %paragraph
  228. return $tom.kindOf(node, "%block");
  229. },
  230. HeadOfParagraph: function(node, processor, range) {
  231. return indentHelper.isCaretOnStartOf(node, range);
  232. },
  233. And: function(judge1, judge2){
  234. return function() {
  235. return judge1.apply(this, arguments) && judge2.apply(this, arguments);
  236. }
  237. },
  238. AlwaysTrue: function() {
  239. return _TRUE;
  240. }
  241. };
  242. Trex.Tool.Indent.Operation = {
  243. /* indent */
  244. GoToBelowTable: function(node, processor) {
  245. var table = $tom.ancestor(node, 'table');
  246. processor.bookmarkToNext(table);
  247. throw $caret_moved;
  248. },
  249. GoToNextCell: function(node, processor) {
  250. var nextCell = indentHelper.findNextCell(node);
  251. if (nextCell) {
  252. processor.selectFirstText(nextCell);
  253. throw $caret_moved;
  254. }
  255. },
  256. IndentListItem: function(node) {
  257. var groupNode = $tom.ancestor(node, 'ul,ol,dl');
  258. if (groupNode) {
  259. var prevSibling = $tom.previous(node);
  260. var nextSibling = $tom.next(node);
  261. if ($tom.kindOf(prevSibling, "ul,ol,dl")) {
  262. // move to previous Group
  263. $tom.append(prevSibling, node);
  264. } else {
  265. var newGroupNode = $tom.clone(groupNode);
  266. $tom.applyStyles(newGroupNode, { marginLeft: _NULL, paddingLeft: _NULL });
  267. $tom.wrap(newGroupNode, node);
  268. }
  269. // move next Siblings to same parent
  270. if ($tom.kindOf(nextSibling, "ul,ol,dl")) {
  271. $tom.moveChild(nextSibling, node.parentNode);
  272. $tom.remove(nextSibling);
  273. }
  274. }
  275. },
  276. getChildrenAsElement: function(node) {
  277. var blocks = [];
  278. var childNodes = node.childNodes;
  279. for (var i = 0, len = childNodes.length; i < len; i++) {
  280. var child = childNodes[i];
  281. if ($tom.isText(child)) {
  282. var wrappedChild = $tom.wrapInlinesWithP(child, node);
  283. blocks.push(wrappedChild);
  284. } else if ($tom.isElement(child)) {
  285. blocks.push(child);
  286. }
  287. }
  288. return blocks;
  289. },
  290. IndentBlockNode: function(node) {
  291. $tom.applyStyles(node, {marginLeft: "+2em"});
  292. },
  293. // AddFourSpaces: function(node, processor) {
  294. // processor.pasteContent("&nbsp;&nbsp;&nbsp;&nbsp;", _FALSE);
  295. // },
  296. /* outdent */
  297. GoToAboveTable: function(node, processor) {
  298. var table = $tom.ancestor(node, 'table');
  299. processor.bookmarkToPrevious(table);
  300. throw $caret_moved;
  301. },
  302. GoToPreviousCell: function(node, processor) {
  303. var previousCell = indentHelper.findPreviousCell(node);
  304. if (previousCell) {
  305. processor.moveCaretTo(previousCell, _TRUE);
  306. throw $caret_moved;
  307. }
  308. },
  309. OutdentListItem: function(node, processor) {
  310. var list = $tom.ancestor(node, 'ul,ol,dl');
  311. if (!list) {
  312. return;
  313. }
  314. var parentNode = list.parentNode;
  315. if ($tom.kindOf(parentNode, "li")) {
  316. $tom.unwrap(parentNode);
  317. parentNode = list.parentNode;
  318. }
  319. var grandParentList = $tom.kindOf(parentNode, 'ul,ol,dl') ? parentNode : _NULL;
  320. var newList;
  321. if (grandParentList) {
  322. newList = $tom.divideNode(list, $tom.indexOf(node));
  323. $tom.insertAt(node, newList);
  324. } else {
  325. newList = $tom.divideNode(list, $tom.indexOf(node));
  326. var cssText = $tom.getStyleText(node);
  327. // list의 스타일을 p에도 적용한다.
  328. var p = processor.newNode('p');
  329. $tom.setStyleText(p, cssText);
  330. $tom.replace(node, p);
  331. $tom.insertAt(p, newList);
  332. }
  333. $tom.removeListIfEmpty(list);
  334. $tom.removeListIfEmpty(newList);
  335. },
  336. OutdentBlockNode: function(node) {
  337. $tom.applyStyles(node, {marginLeft: "-2em"});
  338. },
  339. Propagate: function() {
  340. throw $propagate;
  341. }
  342. };
  343. })();