styledlist.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. /**
  2. * @fileoverview
  3. * 여러 Style의 리스트를 삽입 할 수 있는 Tool 'styledlist' Source,
  4. * Class Trex.Tool.StyledList 와 configuration을 포함
  5. *
  6. */
  7. TrexMessage.addMsg({
  8. '@styledlist.subtitle1': '취소',
  9. '@styledlist.subtitle2': '동그라미',
  10. '@styledlist.subtitle3': '네모',
  11. '@styledlist.subtitle4': '숫자',
  12. '@styledlist.subtitle5': '로마숫자',
  13. '@styledlist.subtitle6': '알파벳'
  14. });
  15. TrexConfig.addTool(
  16. "styledlist",
  17. {
  18. status: _TRUE,
  19. options: [
  20. { label: TXMSG('@styledlist.subtitle1'), title: 'cancel', type: 'cancel', data: 'cancel', klass: 'tx-styledlist-0' },
  21. { label: TXMSG('@styledlist.subtitle2'), title: 'disc', type: 'ul', data: 'disc', klass: 'tx-styledlist-1' },
  22. { label: TXMSG('@styledlist.subtitle3'), title: 'square', type: 'ul', data: 'square', klass: 'tx-styledlist-2' },
  23. { label: TXMSG('@styledlist.subtitle4'), title: 'decimal', type: 'ol', data: 'decimal', klass: 'tx-styledlist-3' },
  24. { label: TXMSG('@styledlist.subtitle5'), title: 'upper-roman', type: 'ol', data: 'upper-roman', klass: 'tx-styledlist-4' },
  25. { label: TXMSG('@styledlist.subtitle6'), title: 'upper-alpha', type: 'ol', data: 'upper-alpha', klass: 'tx-styledlist-5' }
  26. ],
  27. hotKey: {
  28. ul: { // ctrl + alt + u
  29. ctrlKey: _TRUE,
  30. altKey: _TRUE,
  31. keyCode: 85
  32. },
  33. ol: { // ctrl + alt + o
  34. ctrlKey: _TRUE,
  35. altKey: _TRUE,
  36. keyCode: 79
  37. }
  38. }
  39. }
  40. );
  41. Trex.Tool.StyledList = Trex.Class.create({
  42. $const: {
  43. __Identity: 'styledlist'
  44. },
  45. $extend: Trex.Tool,
  46. oninitialized: function(config) {
  47. var self = this;
  48. self.createListStyleMap(config);
  49. self.weave(
  50. new Trex.Button.StyledList(self.buttonCfg),
  51. new Trex.Menu.Select(self.menuCfg),
  52. self.handler,
  53. self.menuInitHandler.bind(self)
  54. );
  55. self.indentHelper = Trex.Tool.Indent.Helper;
  56. self.bindKeyboard(config.hotKey.ul, self.handler.bind(self, "disc"));
  57. self.bindKeyboard(config.hotKey.ol, self.handler.bind(self, "decimal"));
  58. self.startSyncButtonWithStyle();
  59. },
  60. createListStyleMap: function(config) {
  61. var listStyleMap = this.listStyleMap = {};
  62. config.options.each(function(option) {
  63. listStyleMap[option.data] = {
  64. type: option.type,
  65. klass: option.klass
  66. };
  67. });
  68. },
  69. handler: function(data) {
  70. var self = this;
  71. if (!self.listStyleMap[data]) {
  72. return;
  73. }
  74. var listTag = self.listStyleMap[data].type;
  75. var listHeadStyle = {listStyleType: data};
  76. self.canvas.execute(function(processor) {
  77. if (listTag == 'cancel') {
  78. self.outdentListItem(processor);
  79. } else {
  80. self.createListFromSelection(processor, listTag, listHeadStyle);
  81. }
  82. });
  83. },
  84. outdentListItem: function(processor) {
  85. processor.executeUsingCaret(function(range, savedCaret) {
  86. var blockNodes = Trex.Tool.Indent.Helper.findBlocksToIndentFromRange(range, processor, savedCaret);
  87. blockNodes.each(function(node) {
  88. Trex.Tool.Indent.Operation.OutdentListItem(node, processor);
  89. });
  90. });
  91. },
  92. createListFromSelection: function(processor, listTag, listHeadStyle) {
  93. var self = this;
  94. processor.executeUsingCaret(function(range, savedCaret) {
  95. var blockNodes = self.indentHelper.findBlocksToIndentFromRange(range, processor, savedCaret);
  96. var listGroups = self.groupEachList(blockNodes);
  97. listGroups.each(function(nodes) {
  98. var builder = new Trex.Tool.StyledList.ListBuilder(processor, listTag, listHeadStyle);
  99. builder.createListForNodes(nodes);
  100. });
  101. });
  102. this._removeBrInListItemForIE(processor);
  103. },
  104. _removeBrInListItemForIE: function(processor) {
  105. // FTDUEDTR-1391
  106. if ($tx.msie_docmode >= 11) {
  107. var range = processor.createGoogRange();
  108. var startNode = range.getStartNode();
  109. if (range.isCollapsed()
  110. && $tom.isElement(startNode)
  111. && $tom.isElement(startNode.firstChild)
  112. && $tom.isTagName(startNode.firstChild, 'br')) {
  113. $tom.remove(startNode.firstChild);
  114. startNode.appendChild(processor.newText(''));
  115. }
  116. }
  117. },
  118. groupEachList: function(blockNodes) {
  119. var indentHelper = this.indentHelper;
  120. var groupsForList = [];
  121. var currentGroup = [];
  122. var previousCell = _NULL;
  123. blockNodes.each(function(node) {
  124. var currentCell = indentHelper.findCurrentCell(node);
  125. // new list group detected
  126. if (currentCell != previousCell) {
  127. if (currentGroup.length > 0) {
  128. groupsForList.push(currentGroup);
  129. currentGroup = [];
  130. }
  131. previousCell = currentCell;
  132. }
  133. currentGroup.push(node);
  134. });
  135. // remained group
  136. if (currentGroup.length > 0) {
  137. groupsForList.push(currentGroup);
  138. }
  139. return groupsForList;
  140. },
  141. menuInitHandler: function() {
  142. var insideList = this.canvas.query(function(processor) {
  143. return !! processor.findNode('%listhead');
  144. });
  145. var elCancel = $tom.collect(this.menu.elMenu, 'li');
  146. if (insideList) {
  147. $tx.show(elCancel);
  148. } else {
  149. $tx.hide(elCancel);
  150. }
  151. },
  152. startSyncButtonWithStyle: function() {
  153. var self = this;
  154. var canvas = self.canvas;
  155. var cachedProperty = self.getDefaultProperty();
  156. canvas.observeJob(Trex.Ev.__CANVAS_PANEL_QUERY_STATUS, function() {
  157. var listHeadStyle = canvas.query(function(processor) {
  158. var node = processor.findNode('%listhead');
  159. return processor.queryStyle(node, 'listStyleType');
  160. });
  161. listHeadStyle = listHeadStyle || self.getDefaultProperty();
  162. if(cachedProperty == listHeadStyle) {
  163. return;
  164. }
  165. var text = self.getButtonClassByValue(listHeadStyle);
  166. self.button.setText(text);
  167. cachedProperty = listHeadStyle;
  168. });
  169. },
  170. getDefaultProperty: function() {
  171. return "decimal";
  172. },
  173. getButtonClassByValue: function(value) {
  174. var listStyleMap = this.listStyleMap;
  175. if(listStyleMap[value]) {
  176. return listStyleMap[value].klass;
  177. } else {
  178. return listStyleMap[this.getDefaultProperty()].klass;
  179. }
  180. }
  181. });
  182. Trex.Button.StyledList = Trex.Class.create({
  183. $extend: Trex.Button.Select,
  184. setText: function(text) {
  185. this.elIcon.className = "tx-icon " + text;
  186. }
  187. });
  188. Trex.Tool.StyledList.ListBuilder = Trex.Class.create({
  189. currentDepth: _NULL,
  190. prepared: _FALSE,
  191. listElement: _NULL,
  192. uselessListCandidate: [],
  193. processor: _NULL,
  194. initialize: function(processor, listTag, listHeadStyle) {
  195. this.processor = processor;
  196. this.listTag = listTag;
  197. this.listStyle = listHeadStyle;
  198. },
  199. createListForNodes: function(nodes) {
  200. var self = this;
  201. var depthList = self.getNodeDepthList(nodes);
  202. depthList.each(function(object) {
  203. var node = object.node;
  204. var depth = object.depth;
  205. if (!self.prepared) {
  206. self.prepareRootList(node, depth);
  207. }
  208. self.adjustDepth(node, depth);
  209. self.appendAsListItem(node);
  210. });
  211. self.cleanupEmptyList();
  212. },
  213. getNodeDepthList: function(list) {
  214. var self = this;
  215. return list.map(function(node) {
  216. return {node: node, depth: self.countDepthOfList(node)};
  217. });
  218. },
  219. countDepthOfList: function (node) {
  220. var count = 0;
  221. var parent = $tom.parent(node);
  222. while (parent && !$tom.isBody(parent)) {
  223. if ($tom.kindOf(parent, "ol,ul")) {
  224. count++;
  225. } else if ($tom.kindOf(parent, "th,td")) {
  226. break;
  227. }
  228. parent = $tom.parent(parent);
  229. }
  230. return (count || 1);
  231. },
  232. prepareRootList: function(node, depth) {
  233. var self = this;
  234. self.listElement = self.createNewList();
  235. var insertionPoint;
  236. if (node.tagName == "LI") {
  237. self.uselessListCandidate.push(node.parentNode);
  238. insertionPoint = $tom.divideNode(node.parentNode, $tom.indexOf(node));
  239. } else {
  240. insertionPoint = node;
  241. }
  242. $tom.insertAt(self.listElement, insertionPoint);
  243. self.currentDepth = depth;
  244. self.listDepth = depth;
  245. self.prepared = _TRUE;
  246. },
  247. adjustDepth: function(node, depth) {
  248. var self = this;
  249. while (depth != self.currentDepth) {
  250. if (depth > self.currentDepth) {
  251. self.increaseDepth();
  252. } else {
  253. self.decreaseDepth();
  254. }
  255. }
  256. },
  257. increaseDepth: function() {
  258. var self = this;
  259. var listElement = self.listElement;
  260. self.currentDepth++;
  261. var subList = self.createNewList();
  262. listElement.appendChild(subList);
  263. self.listElement = subList;
  264. },
  265. decreaseDepth: function() {
  266. var self = this;
  267. var listElement = self.listElement;
  268. self.currentDepth--;
  269. if (self.listDepth > self.currentDepth) { // 새로 만든 listgroup의 depth가 부족하기 때문에 최상위에 listgroup을 추가해서 트리를 키운다.
  270. self.uselessListCandidate.push(listElement.parentNode);
  271. var insertPosition = $tom.divideNode(listElement.parentNode, $tom.indexOf(listElement));
  272. var newList = self.createNewList();
  273. $tom.insertAt(newList, insertPosition);
  274. newList.appendChild(listElement);
  275. }
  276. self.listElement = listElement.parentNode;
  277. },
  278. createNewList: function() {
  279. var self = this;
  280. var newList = self.processor.newNode(self.listTag);
  281. $tx.setStyle(newList, self.listStyle);
  282. return newList;
  283. },
  284. cleanupEmptyList: function() {
  285. this.uselessListCandidate.each(function(node) {
  286. $tom.removeListIfEmpty(node);
  287. });
  288. },
  289. wrapWithListItem: function(node) {
  290. if (node.tagName == "LI") {
  291. return node;
  292. } else if (node.tagName == "P" || ($tx.webkit && node.tagName == "DIV")) {
  293. // p에 스타일이 있으면 marginLeft는 지우고 li로 감싸기
  294. var newListItem = this.createListItem();
  295. $tom.applyStyles(node, {marginLeft: _NULL});
  296. if ($tom.getStyleText(node)) {
  297. $tom.wrap(newListItem, node);
  298. return newListItem;
  299. } else {
  300. return $tom.replace(node, newListItem);
  301. }
  302. } else {
  303. var li = this.createListItem();
  304. li.appendChild(node);
  305. return li;
  306. }
  307. },
  308. createListItem: function() {
  309. return this.processor.newNode("li");
  310. },
  311. appendAsListItem: function(node) {
  312. var listItem = this.wrapWithListItem(node);
  313. if ($tom.kindOf(node.parentNode, "%listhead")) {
  314. this.uselessListCandidate.push(node.parentNode);
  315. }
  316. this.listElement.appendChild(listItem);
  317. }
  318. });