processor_standard.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. /**
  2. * @fileOverview
  3. * Wysiwyg 영역의 컨텐츠를 조작하기 위해 사용되는 공통되는 Processor 정의
  4. */
  5. Trex.I.Processor = {};
  6. Trex.I.Processor.Standard = /** @lends Trex.Canvas.Processor.prototype */{
  7. txSelection: _NULL,
  8. isRangeInsideWysiwyg: _FALSE,
  9. lastRange: _NULL,
  10. initialize: function(win, doc) {
  11. this.win = win;
  12. this.doc = doc;
  13. this.txSelection = new Trex.Canvas.Selection(this);
  14. this.bookmark = new Trex.Canvas.Bookmark(this);
  15. },
  16. /**
  17. * Trex.Canvas.Selection 객체를 리턴한다.
  18. * @returns {Object} - Trex.Canvas.Selection 객체
  19. * @example
  20. * processor.getTxSel();
  21. */
  22. getTxSel: function() {
  23. return this.txSelection;
  24. },
  25. /**
  26. * native selection object를 리턴한다.
  27. * @returns {Object} - native selection 객체
  28. * @example
  29. * processor.getSel();
  30. */
  31. getSel: function(){
  32. return this.txSelection.getSel();
  33. },
  34. /**
  35. * native range object를 리턴한다.
  36. * @returns {Object} - native range 객체
  37. * @example
  38. * processor.getRange();
  39. */
  40. getRange: function() {
  41. return this.txSelection.getRange();
  42. },
  43. /**
  44. * Trex.Canvas.Bookmark 객체를 리턴한다.
  45. * @returns {Object} - Trex.Canvas.Bookmark 객체
  46. * @example
  47. * processor.getBookmark();
  48. */
  49. getBookmark: function() {
  50. return this.bookmark;
  51. },
  52. /**
  53. * 선택된 영역의 collapse 여부(선택된 영역이 있는지 여부)를 리턴한다.
  54. * @returns {Boolean} - collapse 여부
  55. * @see Trex.Canvas.Selection#isCollapsed
  56. * @example
  57. * processor.isCollapsed();
  58. */
  59. isCollapsed: function() {
  60. return this.txSelection.isCollapsed();
  61. },
  62. /**
  63. * 선택된 영역의 노드를 리턴한다.
  64. * @returns {Element} - 선택된 영역의 노드
  65. * @see Trex.Canvas.Selection#getNode
  66. * @example
  67. * processor.getNode();
  68. */
  69. getNode: function() {
  70. return this.txSelection.getNode();
  71. },
  72. /**
  73. * 선택된 영역의 컨트롤 노드(img,object,hr,table,button)를 리턴한다.
  74. * @returns {Element} - 선택된 영역의 노드
  75. * @see Trex.Canvas.Selection#getControl
  76. * @example
  77. * processor.getControl();
  78. */
  79. getControl: function(){
  80. return this.txSelection.getControl();
  81. },
  82. /**
  83. * 선택된 영역이 컨트롤 노드인지 여부를 리턴한다.
  84. * @returns {Boolean} - 컨트롤 노드인지 여부
  85. * @see Trex.Canvas.Selection#hasControl
  86. * @example
  87. * processor.hasControl();
  88. */
  89. hasControl: function(){
  90. return this.txSelection.hasControl();
  91. },
  92. /**
  93. * 컨트롤 노드를 선택한다.
  94. * @param {Element} node - 컨트롤 노트
  95. * @example
  96. * txSelection.selectControl(node);
  97. */
  98. selectControl: function(node){
  99. return this.txSelection.selectControl(node);
  100. },
  101. /**
  102. * 선택된 영역의 텍스트 데이터를 리턴한다.
  103. * @returns {String} - 선택된 영역의 텍스트 데이터
  104. * @see Trex.Canvas.Selection#getText
  105. * @example
  106. * processor.getText();
  107. */
  108. getText: function(){
  109. return this.txSelection.getText();
  110. },
  111. /**
  112. * 선택된 영역이 텍스트 데이터 영역의 어떤 위치인지를 리턴한다.
  113. * @returns {Number} - 텍스트 데이터 영역의 어떤 위치인지 <br/>
  114. * 텍스트의 처음 : $tom.__POSITION.__START_OF_TEXT : -1<br/>
  115. * 텍스트의 중간 : $tom.__POSITION.__MIDDLE_OF_TEXT : 0<br/>
  116. * 텍스트의 마지막 : $tom.__POSITION.__END_OF_TEXT : 1
  117. * @see Trex.Canvas.Selection#compareTextPos
  118. * @example
  119. * processor.compareTextPos();
  120. */
  121. compareTextPos: function() {
  122. return this.txSelection.compareTextPos();
  123. },
  124. /**
  125. * 현재 선택된 영역에서 조건에 맞는 노드를 리턴한다.
  126. * @param {Function, String} filter - 조건을 나타내는 함수 또는 문자열
  127. * @returns {Element} - 조건에 맞는 노드
  128. * @example
  129. * processor.findNode(function() { return 'p,div'; });
  130. * processor.findNode('%paragraph');
  131. */
  132. findNode: function(filter) {
  133. try {
  134. return $tom.find(this.getNode(), filter);
  135. } catch(e) {
  136. return _NULL;
  137. }
  138. },
  139. /*-------- processor - query style start --------*/
  140. /**
  141. * 특정한 노드의 특정한 스타일 값을 얻어온다.
  142. * @param {Element} node - 특정 노드
  143. * @param {String} styleName - 스타일 명
  144. * @returns {String} - 스타일 값
  145. * @example
  146. * processor.queryStyle(node, 'textAlign');
  147. */
  148. queryStyle: function(node, styleName) {
  149. if(!node) {
  150. return _NULL;
  151. }
  152. styleName = ((styleName == 'float')? ((node.style.styleFloat === _UNDEFINED)? 'cssFloat': 'styleFloat'): styleName);
  153. if(node.style && node.style[styleName]) {
  154. return node.style[styleName];
  155. } else if(node.currentStyle && node.currentStyle[styleName]) {
  156. return node.currentStyle[styleName];
  157. } else if(_WIN.getComputedStyle) {
  158. var targetNode = node;
  159. while($tom.isText(targetNode)){
  160. targetNode = targetNode.parentNode;
  161. }
  162. var _cssStyle = this.doc.defaultView.getComputedStyle(targetNode, _NULL);
  163. return ((_cssStyle)? _cssStyle[styleName] : _NULL);
  164. }
  165. return _NULL;
  166. },
  167. /**
  168. * 특정한 노드의 특정한 속성 값을 얻어온다.
  169. * @param {Element} node - 특정 노드
  170. * @param {String} attrName - 속성 명
  171. * @returns {String} - 속성 값
  172. * @example
  173. * processor.queryAttr(node, 'align');
  174. */
  175. queryAttr: function(node, attrName) {
  176. if(!node) {
  177. return _NULL;
  178. }
  179. return $tom.getAttribute(node, attrName);
  180. },
  181. /**
  182. * 선택된 영역의 native queryCommandState 값을 얻어온다.
  183. * @param {String} command - 커맨드 명
  184. * @returns {Boolean} - 해당 영역이 커맨드 상태인지 여부
  185. * @example
  186. * processor.queryCommandState('bold');
  187. */
  188. queryCommandState: function(command) {
  189. try {
  190. return this.doc.queryCommandState(command);
  191. } catch(e) { return _FALSE; }
  192. },
  193. /**
  194. * 선택된 영역의 native queryCommandValue 값을 얻어온다.
  195. */
  196. queryCommandValue: function(command) {
  197. try {
  198. return this.doc.queryCommandValue(command);
  199. } catch(e) {
  200. return "";
  201. }
  202. },
  203. /*-------- processor - query style end --------*/
  204. /**
  205. * 선택된 영역에 native execCommand를 실행시킨다.
  206. * @param {String} command - 커맨드 명
  207. * @param {String} data - 데이터 값
  208. * @example
  209. * processor.execCommand('forecolor', '#333');
  210. */
  211. execCommand: function(command, data) {
  212. if ($tx.gecko) {
  213. // Firefox는 styleWithCSS 기본값이 true
  214. try {
  215. this.doc.execCommand('styleWithCSS', _FALSE, _FALSE);
  216. } catch(e) {}
  217. }
  218. try {
  219. this.doc.execCommand(command, _FALSE, data);
  220. } catch(e) {}
  221. },
  222. /*-------- processor - marker start --------*/
  223. /**
  224. * 선택된 영역에 주어진 handler를 실행시킨다.
  225. * 주로 외부에서 processor를 이용해 DOM조작을 할 경우에 사용된다.
  226. * @param {Funtion} handler - 해당 영역에 실행할 함수
  227. * @example
  228. * processor.execWithMarker(function(marker) {
  229. * $tom.insertAt(node, marker.endMarker);
  230. * });
  231. */
  232. execWithMarker: function(handler) {
  233. var _marker = new Trex.Canvas.Marker(this);
  234. this.bookmarkTo();
  235. try {
  236. _marker.paste();
  237. _marker.backup();
  238. handler(_marker);
  239. } catch(e) {
  240. console.log(e, e.stack)
  241. } finally {
  242. _marker.remove();
  243. }
  244. },
  245. /*-------- processor - marker end --------*/
  246. /*--------------------- focus, movecaret ----------------------*/
  247. /**
  248. * wysiwyg 영역에 포커스를 준다.
  249. * @example
  250. * processor.focus();
  251. */
  252. focus: function() {
  253. this.doc.body.focus();
  254. },
  255. /**
  256. * wysiwyg 영역에 포커스를 뺀다.
  257. * @example
  258. * processor.blur();
  259. */
  260. blur: function() {
  261. _WIN.focus(); //NOTE: by focused on parent window, editor will be blured
  262. },
  263. /**
  264. * 본문의 처음으로 캐럿을 옮긴다.
  265. * @example
  266. * processor.focusOnTop();
  267. */
  268. focusOnTop: function() {
  269. this.focus();
  270. this.selectFirstText(this.doc.body);
  271. this.doc.body.scrollTop = 0; //NOTE: only html, not xhtml
  272. },
  273. selectFirstText: function(node) {
  274. var firstNode = $tom.top(node);
  275. var range = this.createGoogRangeFromNodes(firstNode, 0, firstNode, 0);
  276. range.select();
  277. },
  278. /**
  279. * 본문의 마지막으로 캐럿을 옮긴다.
  280. * @example
  281. * processor.focusOnBottom();
  282. */
  283. focusOnBottom: function() {
  284. this.focus();
  285. this.moveCaretTo(this.doc.body, _FALSE);
  286. this.doc.body.scrollTop = this.doc.body.scrollHeight; //NOTE: only html, not xhtml
  287. },
  288. /**
  289. * 특정 노드로 캐럿을 옮긴다.
  290. * @param {Element} node - 특정 노드
  291. * @param {Boolean} toStart - 위치, 시작 = true
  292. * @example
  293. * processor.moveCaretTo(node, true);
  294. */
  295. moveCaretTo: function(node, toStart) {
  296. if(!node) {
  297. return;
  298. }
  299. this.focus();
  300. this.bookmarkInto(node, toStart);
  301. this.bookmark.select(this.txSelection);
  302. },
  303. /**
  304. * 특정 노드의 바깥으로 캐럿을 옮긴다.
  305. * @param {String} scope - 특정 노드 패턴
  306. * @example
  307. * processor.moveCaretWith(scope);
  308. */
  309. moveCaretWith: function(scope) {
  310. if(!scope) { return; }
  311. var _elOuter = this.findNode(scope);
  312. if(_elOuter) {
  313. this.focus();
  314. this.bookmark.saveNextTo(_elOuter);
  315. this.bookmark.select(this.txSelection);
  316. }
  317. },
  318. /**
  319. * 특정 노드를 감싸 선택한다.
  320. * @param {Element} node - 특정 노드
  321. * @example
  322. * processor.selectAround(node);
  323. */
  324. selectAround: function(node) {
  325. if(!node) {
  326. return;
  327. }
  328. this.focus();
  329. this.bookmark.saveAroundNode(node);
  330. this.bookmark.select(this.txSelection);
  331. },
  332. /**
  333. * 특정 노드의 안으로 북마크를 수정한다.
  334. * @param {Element} node - 특정 노드
  335. * @example
  336. * processor.bookmarkInto(node);
  337. */
  338. bookmarkInto: function(node, toStart) {
  339. if(!node) {
  340. return;
  341. }
  342. toStart = (toStart == _NULL)? _TRUE: toStart;
  343. if(toStart) {
  344. this.bookmark.saveIntoFirst(node);
  345. } else {
  346. this.bookmark.saveIntoLast(node);
  347. }
  348. },
  349. /**
  350. * 특정 노드의 이전으로 북마크를 수정한다.
  351. * @param {Element} node - 특정 노드
  352. * @example
  353. * processor.bookmarkToPrevious(node);
  354. */
  355. bookmarkToPrevious: function(node) {
  356. if(!node) {
  357. return;
  358. }
  359. this.bookmark.savePreviousTo(node);
  360. },
  361. /**
  362. * 특정 노드의 다음으로 북마크를 수정한다.
  363. * @param {Element} node - 특정 노드
  364. * @example
  365. * processor.bookmarkToNext(node);
  366. */
  367. bookmarkToNext: function(node) {
  368. if(!node) {
  369. return;
  370. }
  371. this.bookmark.saveNextTo(node);
  372. },
  373. /**
  374. * execute하기 전 range를 북마크한다.
  375. * @example
  376. * processor.bookmark();
  377. */
  378. bookmarkTo: function(rng) {
  379. rng = rng || this.txSelection.getRange();
  380. this.bookmark.save({
  381. startContainer: rng.startContainer,
  382. startOffset: rng.startOffset,
  383. endContainer: rng.endContainer,
  384. endOffset: rng.endOffset
  385. });
  386. },
  387. /**
  388. * marker에 따라 북마크를 수정한다.
  389. * @example
  390. * processor.bookmarkWithMarker(marker);
  391. */
  392. bookmarkWithMarker: function(marker) {
  393. this.bookmark.saveWithMarker(marker);
  394. },
  395. /**
  396. * 저장한 range를 선택한다.
  397. * @example
  398. * processor.restore();
  399. */
  400. restore: function() {
  401. this.bookmark.select(this.txSelection);
  402. },
  403. /*------------ execute action ------------*/
  404. /**
  405. * 인자에 따라 노드를 생성한다.
  406. * @param {String, Object, Element} argument - 가변 arguments<br/>
  407. * {String} name : 1st String은 노드명 <br/>
  408. * {Object} attributes : 적용할 속성들 <br/>
  409. * {Element, String, Number} children : 자식 노드
  410. * @example
  411. * processor.create('div', { 'className': 'txc-textbox' });
  412. */
  413. create: function() {
  414. var name = arguments[0];
  415. var _node = this.newNode(name);
  416. for (var i = 1; i < arguments.length; i++) {
  417. var arg = arguments[i];
  418. if (arg.nodeType) {
  419. $tom.append(_node, arg);
  420. } else if (typeof(arg) == 'string' || typeof(arg) == 'number') {
  421. _node.innerHTML += arg;
  422. } else if (typeof(arg) == 'array') {
  423. for (var j = 0; j < arg.length; j++) {
  424. $tom.append(_node, arg[j]);
  425. }
  426. } else {
  427. $tom.applyAttributes(_node, arg);
  428. }
  429. }
  430. return _node;
  431. },
  432. /**
  433. * 선택한 영역에 노드를 삽입한다.
  434. * @param {Array,Element} nodes - 삽입하고자 하는 노드 배열 또는 노드
  435. * @param {Boolean} newline - 현재 영역에서 한줄을 띄운 후 삽입할지 여부
  436. * @param {Object} wrapStyle - wrapper 노드에 적용할 스타일, <br/>
  437. * newline이 true 일 경우에만 의미를 갖는다.
  438. * @example
  439. * processor.pasteNode([node, node], true, { 'style': { 'textAlign': 'center' } });
  440. */
  441. pasteNode: function(nodes, newline, wrapStyle) {
  442. if(!nodes) {
  443. return;
  444. }
  445. if(!nodes.length) {
  446. nodes = [].concat(nodes);
  447. }
  448. this.txSelection.collapse(_FALSE);
  449. if(newline) {
  450. /* order
  451. * (curNode) > wpNode > dvNode
  452. */
  453. var _curNode, _wpNode, _dvNode;
  454. var _processor = this;
  455. this.execWithMarker(function(marker) {
  456. _dvNode = $tom.divideParagraph(marker.endMarker);
  457. var _detected = $tom.kindOf(_dvNode, 'p,li,dd,dt,h1,h2,h3,h4,h5,h6');
  458. if(_detected) {
  459. _curNode = $tom.previous(_dvNode);
  460. _wpNode = $tom.clone(_dvNode);
  461. } else {
  462. _dvNode = _processor.newNode('p');
  463. $tom.insertAt(_dvNode, marker.endMarker);
  464. _wpNode = _processor.newNode('p');
  465. }
  466. $tom.insertAt(_wpNode, _dvNode);
  467. nodes.each(function(node) {
  468. $tom.append(_wpNode, node);
  469. });
  470. if(wrapStyle) {
  471. $tom.applyAttributes(_wpNode, wrapStyle);
  472. }
  473. // #FTDUEDTR-1442
  474. if (nodes.length == 1) {
  475. var firstChildNode = nodes[0];
  476. var disableBlockTag = $tom.kindOf(firstChildNode, 'table,hr,blockquote,pre,h1,h2,h3,h4,h5,h6,div');
  477. var isParagraphTag = $tom.isTagName(_wpNode, 'p');
  478. if (disableBlockTag && isParagraphTag) {
  479. $tom.unwrap(_wpNode);
  480. }
  481. }
  482. });
  483. if(_curNode) {
  484. if(!$tom.hasData(_curNode)) {
  485. this.stuffNode(_curNode);
  486. }
  487. }
  488. this.stuffNode(_dvNode);
  489. this.bookmark.saveIntoFirst(_dvNode);
  490. } else {
  491. var self = this;
  492. this.executeUsingCaret(function(range, savedCaret) {
  493. var startCaret = savedCaret.getCaret(_FALSE),
  494. endCaret = savedCaret.getCaret(_FALSE);
  495. var targetNode = $tx.msie_nonstd ? startCaret : _NULL;
  496. nodes.each(function(node) {
  497. range.insertNode(node, targetNode);
  498. });
  499. if (endCaret && endCaret.nextSibling) {
  500. self.moveCaretTo(endCaret.nextSibling, 0);
  501. }
  502. });
  503. }
  504. return nodes[0];
  505. },
  506. /**
  507. * 선택한 영역에 HTML 컨텐츠를 삽입한다.
  508. * @param {String} html - 삽입하고자 하는 HTML 컨텐츠
  509. * @param {Boolean} newline - 현재 영역에서 한줄을 띄운 후 삽입할지 여부 true/false
  510. * @param {Object} wrapStyle - wrapper 노드에 적용할 스타일, <br/>
  511. * newline이 true 일 경우에만 의미를 갖는다.
  512. * @example
  513. * processor.pasteNode('<img src="이미지경로"/>', true, { 'style': { 'textAlign': 'center' } });
  514. */
  515. pasteContent: function(html, newline, wrapStyle) {
  516. var _tmpNode = this.create('div');
  517. _tmpNode.innerHTML = html;
  518. var _dataNodes = $tom.children(_tmpNode);
  519. return this.pasteNode(_dataNodes, newline, wrapStyle);
  520. },
  521. /**
  522. * 주어진 노드를 새로운 노드로 교체한다.
  523. * @param {Element} node - 기존 노드
  524. * @param {String} tag - 새로운 노드 명
  525. * @param {Object} attributes - 새로운 노드에 적용할 속성들
  526. * @returns {Element} - 생성한 노드
  527. * @example
  528. * processor.replace(p, 'li');
  529. */
  530. replace: function(node, tag, attributes) {
  531. this.bookmark.saveAroundNode(node);
  532. return $tom.replace(node, this.create(tag, attributes));
  533. },
  534. /**
  535. * 선택한 영역안에 있는 노드 중에 패턴을 만족하는 블럭 노드들을 리턴한다.
  536. * @param {Array} filter - 수집할 노드 패턴 조건
  537. * @returns {Array} - 선택한 영역안에 있는 노드 중에 패턴을 만족하는 노드들
  538. * @example
  539. * processor.blocks(function() {
  540. return '%paragraph';
  541. });
  542. */
  543. blocks: function(filter) {
  544. var _nodes = [];
  545. var _patterns = filter();
  546. if (this.hasControl()) {
  547. var _control = this.getControl();
  548. if ($tom.kindOf(_control.parentNode, _patterns)) {
  549. _nodes.push(_control.parentNode);
  550. }
  551. } else {
  552. var _processor = this;
  553. this.execWithMarker(function(marker) {
  554. var _itr = _processor.getBlockRangeIterator(_patterns, marker.startMarker, marker.endMarker);
  555. var _node;
  556. while (_itr.hasNext()) {
  557. _node = _itr.next();
  558. if ($tom.kindOf(_node, '#tx_start_marker,#tx_end_marker')) {
  559. //ignore
  560. } else {
  561. _nodes.push(_node);
  562. }
  563. }
  564. });
  565. }
  566. return _nodes;
  567. },
  568. /**
  569. * 선택한 영역안에 있는 노드 중에 패턴을 만족하는 인라인 노드들을 리턴한다.
  570. * @param {Array} filter - 수집할 노드 패턴 조건
  571. * @returns {Array} - 선택한 영역안에 있는 노드 중에 패턴을 만족하는 노드들
  572. * @example
  573. * processor.inlines(function(type) {
  574. if(type === 'control') {
  575. return 'hr,table';
  576. }
  577. return '%inline';
  578. });
  579. */
  580. inlines: function(filter) {
  581. var _nodes = [];
  582. var _patterns = filter();
  583. var _processor = this;
  584. var _createInline = function() {
  585. return _processor.create($tom.inlineOf());
  586. };
  587. if (this.hasControl()) {
  588. var _control = this.getControl();
  589. if ($tom.kindOf(_control, _patterns)) {
  590. _nodes.push(_control);
  591. } else {
  592. var _iNode = _createInline();
  593. $tom.insertNext(_iNode, _control);
  594. $tom.append(_iNode, _control);
  595. }
  596. } else {
  597. this.execWithMarker(function(marker) {
  598. if (marker.checkCollapsed()) { //collapsed
  599. var _iNode = _createInline();
  600. $tom.append(_iNode, _processor.newDummy());
  601. $tom.insertNext(_iNode, marker.startMarker);
  602. _processor.bookmarkTo({
  603. startContainer: _iNode,
  604. startOffset: 1,
  605. endContainer: _iNode,
  606. endOffset: 1
  607. });
  608. _nodes.push(_iNode);
  609. } else {
  610. var _itr = _processor.getInlineRangeIterator(_patterns, marker.startMarker, marker.endMarker);
  611. var _node;
  612. while (_itr.hasNext()) {
  613. _node = _itr.next();
  614. if ($tom.kindOf(_node, '#tx_start_marker,#tx_end_marker')) {
  615. //ignore
  616. } else if ($tom.kindOf(_node, 'br')) {
  617. //ignore
  618. } else {
  619. _nodes.push(_node);
  620. }
  621. }
  622. }
  623. });
  624. }
  625. return _nodes;
  626. },
  627. /**
  628. * 선택한 영역안에 있는 노드 중에 패턴을 만족하는 컨트롤 노드(img,object,hr,table,button)들을 리턴한다.
  629. * @param {Array} filter - 수집할 노드 패턴 조건
  630. * @returns {Array} - 선택한 영역안에 있는 노드 중에 패턴을 만족하는 노드들
  631. * @example
  632. * processor.controls(function() {
  633. return 'hr,table';
  634. });
  635. */
  636. controls: function(filter) {
  637. var _nodes = [];
  638. if (this.hasControl()) {
  639. if ($tom.kindOf(this.getControl(), filter())) {
  640. _nodes.push(this.getControl());
  641. }
  642. }
  643. return _nodes;
  644. },
  645. /**
  646. * 더미용 nbsp를 넣는 함수.
  647. * webkit 용에 구현되어있습니다.
  648. * Safari 에서 apply 시
  649. * 폰트에 대한 속성을 잃어버리기 때문에 필요.
  650. */
  651. addDummyNbsp: function () {},
  652. /**
  653. * 배열 내의 모든 노드에게 지정한 속성을 적용한다.
  654. * @param {Array} nodes - 속성을 적용할 노드 배열
  655. * @param {Object} attributes - 노드에 적용할 속성들
  656. * @returns {Array} - 입력 노드들
  657. * @example
  658. * processor.apply([p,p,p], { style: { textAlign: 'center'}});
  659. */
  660. apply: function(nodes, attributes) {
  661. if(!nodes) {
  662. return _NULL;
  663. }
  664. if(!nodes.length) {
  665. nodes = [].concat(nodes);
  666. }
  667. nodes.each(function(node) {
  668. $tom.applyAttributes(node, attributes);
  669. });
  670. return nodes;
  671. },
  672. /**
  673. * 배열 내의 모든 노드를 주어진 블럭으로 감싼다.
  674. * @param {Array} nodes - 블럭으로 감쌀 노드 배열
  675. * @param {String} tag - 블럭 노드 명
  676. * @param {Object} attributes - 블럭에 적용할 속성
  677. * @returns {Element} - 생성한 블럭노드
  678. * @example
  679. * processor.wrap([p,p,p], 'div', { style: { backgroundColor: 'black'}});
  680. */
  681. wrap: function(nodes, tag, attributes) {
  682. if(!nodes) {
  683. return _NULL;
  684. }
  685. if(!nodes.length) {
  686. nodes = [].concat(nodes);
  687. }
  688. attributes = attributes || {};
  689. var res = $tom.wrap(this.create(tag, attributes), nodes);
  690. if($tx.msie && !$tom.nextContent(res)){
  691. var e = this.doc.createElement('p');
  692. e.innerHTML = $tom.EMPTY_BOGUS;
  693. this.doc.body.appendChild(e);
  694. }
  695. return res;
  696. },
  697. /**
  698. * 블럭으로 감싸진 노드들을 빼내고 블럭을 삭제한다.
  699. * @param {Element} node - 블럭 노드
  700. * @returns {Element} - 블럭의 첫번째 노드 또는 블럭의 다음 노드
  701. * @example
  702. * processor.unwrap(node);
  703. */
  704. unwrap: function(node) {
  705. if (!node) {
  706. return _NULL;
  707. }
  708. this.bookmark.saveAroundNode(node);
  709. return $tom.unwrap(node);
  710. },
  711. createGoogRange: function() {
  712. return goog.dom.Range.createFromWindow(this.win)
  713. },
  714. createGoogRangeFromNodes: function(startNode, startOffset, endNode, endOffset) {
  715. return goog.dom.Range.createFromNodes(startNode, startOffset, endNode, endOffset);
  716. },
  717. executeUsingCaret: function(handler) {
  718. try {
  719. var range = this.createGoogRange();
  720. var savedCaret = range.saveUsingCarets();
  721. return handler(range, savedCaret);
  722. } finally {
  723. if (!savedCaret.isDisposed()) {
  724. savedCaret.restore();
  725. }
  726. }
  727. }
  728. };
  729. Trex.module("observe that @when control elements are focused at",
  730. function(editor, toolbar, sidebar, canvas) {
  731. if($tx.webkit || $tx.presto) {
  732. canvas.observeJob(Trex.Ev.__CANVAS_PANEL_MOUSEDOWN, function(ev) {
  733. var _processor = canvas.getProcessor();
  734. var _node = $tx.element(ev);
  735. if ($tom.kindOf(_node, "img,hr,iframe,table")) {
  736. var _button = $tom.find(_node, 'button');
  737. if(_button) {
  738. _processor.selectControl(_button);
  739. } else {
  740. _processor.selectControl(_node);
  741. }
  742. } else if ($tom.kindOf(_node, "button")) {
  743. _processor.selectControl(_node);
  744. }
  745. });
  746. }
  747. }
  748. );
  749. Trex.module("bind iframe activate or deactivate event",
  750. function(editor, toolbar, sidebar, canvas) {
  751. // if ($tx.msie_nonstd) {
  752. canvas.observeJob(Trex.Ev.__IFRAME_LOAD_COMPLETE, function(panelDoc) {
  753. var _processor = canvas.getProcessor(Trex.Canvas.__WYSIWYG_MODE);
  754. $tx.observe(panelDoc, 'beforedeactivate', function(ev) {
  755. _processor.isRangeInsideWysiwyg = true;
  756. _processor.lastRange = _processor.getRange();
  757. });
  758. $tx.observe(panelDoc, 'deactivate', function (ev) {
  759. if (_processor.hasControl()) {
  760. return;
  761. }
  762. _processor.isRangeInsideWysiwyg = false;
  763. });
  764. $tx.observe(panelDoc, 'activate', function() {
  765. _processor.isRangeInsideWysiwyg = true;
  766. _processor.lastRange = _NULL;
  767. });
  768. });
  769. // }
  770. }
  771. );