canvas.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906
  1. /**
  2. * @fileOverview
  3. * 컨텐츠를 가지고 있는 편집 영역을 수정, 관리하는 Trex.Canvas 관련 Source로
  4. * 대부분 각 panel들에게 행동들을 위임한다.
  5. * 편집 영역 = panel = TextPanel, HtmlPanel, WysiwygPanel
  6. */
  7. (function(Trex) {
  8. var QUERY_TRIGGER_KEYCODES = new $tx.Set(13, 8, 32, 33, 34, 37, 38, 39, 40, 46);
  9. var shouldTriggerQuery = function(keyCode) {
  10. return QUERY_TRIGGER_KEYCODES.contains(keyCode);
  11. };
  12. TrexConfig.add({
  13. "canvas": {
  14. doctype: "auto", // edge
  15. mode: ["text", "html", "source"],
  16. styles: {
  17. color: "#333333",
  18. fontFamily: "돋움",
  19. fontSize: "9pt",
  20. backgroundColor: "#ffffff",
  21. lineHeight: "1.5",
  22. padding: "8px"
  23. },
  24. pMarginZero: true,
  25. selectedMode: "html",
  26. readonly: _FALSE,
  27. initHeight: 400,
  28. minHeight: 200,
  29. ext: 'html',
  30. param: "",
  31. newlinepolicy: "p",
  32. showGuideArea: _TRUE,
  33. convertingText: _TRUE,
  34. escapeTextModeContents: _TRUE,
  35. removeTextModeBr: _FALSE,
  36. respectVisibilityInDesign: _TRUE
  37. }
  38. }, function(root) {
  39. var _config = TrexConfig.get('canvas', root);
  40. var _evConfig = root.events;
  41. _config.initializedId = root.initializedId || '';
  42. _config.useHotKey = _evConfig.useHotKey;
  43. var _switcher = TrexConfig.getTool('switcher', root);
  44. if (Trex.available(_switcher, "switcher" + _config.initializedId)) {
  45. _config.mode = _switcher.options.pluck("data");
  46. }
  47. var _fontfamily = TrexConfig.getTool('fontfamily', root);
  48. if (Trex.available(_fontfamily, "fontfamily" + _config.initializedId)) {
  49. if(_fontfamily.webfont && _fontfamily.webfont.use) {
  50. _config.webfont = _fontfamily.webfont;
  51. _config.webfont.options.each(function(element) {
  52. element.url = TrexConfig.getUrl(element.url);
  53. });
  54. }
  55. }
  56. var _resizer = TrexConfig.get('resizer', root);
  57. if (_resizer) {
  58. _config.minHeight = _resizer.minHeight;
  59. }
  60. /**
  61. * 에디터통합 버전으로 한메일 배포시에는
  62. * 윗줄 주석해제, 아랫줄 삭제
  63. */
  64. //_config.wysiwygUrl = TrexConfig.getUrl(["#host#path/pages/daumx/", "wysiwyg_", (_config.serviceWysiwyg || "" ), ((_config.doctype == "html") ? "html" : "xhtml"), ".", (_config.ext ? _config.ext : "html"), "?prefix=" + root.initializedId, "&", _config.param].join(""));
  65. _config.wysiwygUrl = TrexConfig.getUrl([(_config.wysiwygPath || "#host#path/pages/daumx/"), "wysiwyg_", (_config.serviceWysiwyg || "" ), ((_config.doctype == "html") ? "html" : "xhtml"), ".", (_config.ext ? _config.ext : "html"), "?prefix=" + root.initializedId, "&", _config.param].join(""));
  66. /**
  67. * doctype 결정기준
  68. */
  69. if (_config.doctype == 'auto') {
  70. if ($tx.msie && $tx.msie_quirks) {
  71. _config.doctype = 'quirks';
  72. } else {
  73. _config.doctype = 'edge';
  74. }
  75. }
  76. });
  77. TrexConfig.add({
  78. "size": {
  79. }
  80. });
  81. /**
  82. * 컨텐츠를 가지고 있는 편집 영역을 수정, 관리하는 Trex.Canvas 객체로 <br/>
  83. * 대부분 각 panel들에게 행동들을 위임한다. <br/>
  84. * 각각의 panel들은 해당 Processor들을 포함한다. <br/>
  85. * 편집 영역 = panel = TextPanel, HtmlPanel, WysiwygPanel
  86. *
  87. * @class
  88. * @extends Trex.I.JobObservable Trex.I.KeyObservable
  89. * @param {Object} editor
  90. * @param {Object} config
  91. */
  92. Trex.Canvas = Trex.Class.create( /** @lends Trex.Canvas.prototype */{
  93. /** @ignore */
  94. $const: {
  95. /** @name Trex.Canvas.__TEXT_MODE */
  96. __TEXT_MODE: "text",
  97. /** @name Trex.Canvas.__HTML_MODE */
  98. __HTML_MODE: "source",
  99. /** @name Trex.Canvas.__WYSIWYG_MODE */
  100. __WYSIWYG_MODE: "html",
  101. __WYSIWYG_PADDING: 8,
  102. __IMAGE_PADDING: 5
  103. },
  104. /** @ignore */
  105. $mixins: [Trex.I.JobObservable, Trex.I.KeyObservable, Trex.I.ElementObservable, Trex.I.MouseoverObservable],
  106. /** Editor instance */
  107. editor: _NULL,
  108. /** Canvas Dom element, Generally $tx('tx_canvas') */
  109. elContainer: _NULL,
  110. /** Canvas Config */
  111. config: _NULL,
  112. /** History Instance for redo/undo */
  113. history: _NULL,
  114. /**
  115. * Panels 객체
  116. * @private
  117. * @example
  118. * canvas.panels['html']
  119. * canvas.panels['source']
  120. * canvas.panels['text']
  121. */
  122. panels: _NULL,
  123. initialize: function(editor, rootConfig) {
  124. this.editor = editor;
  125. var _config = this.config = TrexConfig.get('canvas', rootConfig);
  126. var _initializedId = ((rootConfig.initializedId) ? rootConfig.initializedId : "");
  127. this.elContainer = $tx("tx_canvas" + _initializedId);
  128. this.wysiwygEl = $tx("tx_canvas_wysiwyg_holder" + _initializedId);
  129. this.sourceEl = $tx("tx_canvas_source_holder" + _initializedId);
  130. this.textEl = $tx("tx_canvas_text_holder" + _initializedId);
  131. this.initConfig(rootConfig);
  132. this.createPanel();
  133. this.history = new Trex.History(this, _config);
  134. this.setCanvasSize({
  135. height: _config.initHeight
  136. });
  137. },
  138. initConfig: function(rootConfig) {
  139. var _config = this.config;
  140. /**
  141. * root config를 얻어온다.
  142. * @private
  143. * @returns {Object} root config
  144. */
  145. this.getRootConfig = function() {
  146. return rootConfig;
  147. };
  148. /**
  149. * Canvas의 config를 가져온다.
  150. * @returns {Object} config
  151. */
  152. this.getConfig = function() {
  153. return _config;
  154. };
  155. /**
  156. * wysiwyg panel의 스타일 config를 가져온다.
  157. * @param {String} name - 스타일명 optional
  158. * @returns {Object} 스타일 config
  159. * @example
  160. * canvas.getStyleConfig();
  161. */
  162. this.getStyleConfig = function(name) {
  163. if(name) {
  164. return _config.styles[name];
  165. } else {
  166. return _config.styles;
  167. }
  168. };
  169. var _sizeConfig = TrexConfig.get('size', rootConfig);
  170. this.measureWrapWidth = function() {
  171. _sizeConfig.wrapWidth = this.getContainerWidth(); // TODO FTDUEDTR-1214
  172. };
  173. this.measureWrapWidth();
  174. if(!_sizeConfig.contentWidth) {
  175. _sizeConfig.contentWidth = _sizeConfig.wrapWidth;
  176. }
  177. _sizeConfig.contentPadding = _config.styles.padding.parsePx(); //15
  178. /**
  179. * canvas size 관련 config를 얻어온다.
  180. * @returns {Object} size config
  181. */
  182. this.getSizeConfig = function() {
  183. return _sizeConfig;
  184. };
  185. },
  186. getContainerWidth: function() {
  187. return $tx.getDimensions(this.elContainer).width;
  188. },
  189. /**
  190. * Panels 객체들을 초기화한다.
  191. * @private
  192. */
  193. createPanel: function() {
  194. var _canvas = this;
  195. var _config = this.config;
  196. this.panels = {};
  197. this.mode = _config.selectedMode || Trex.Canvas.__WYSIWYG_MODE;
  198. if (this._isForceTextMode()) {
  199. this.mode = Trex.Canvas.__TEXT_MODE;
  200. }
  201. var _panelCreater = {
  202. "text": function(_config) {
  203. return new Trex.Canvas.TextPanel(_canvas, _config);
  204. },
  205. "source": function(_config) {
  206. return new Trex.Canvas.HtmlPanel(_canvas, _config);
  207. },
  208. "html": function(_config) {
  209. return new Trex.Canvas.WysiwygPanel(_canvas, _config);
  210. }
  211. };
  212. _config.mode.each(function(name) {
  213. if (_panelCreater[name]) {
  214. _canvas.panels[name] = _panelCreater[name](_config);
  215. }
  216. });
  217. for(var _p in _canvas.panels) {
  218. if (this.mode == _p) {
  219. _canvas.panels[_p].show();
  220. } else {
  221. _canvas.panels[_p].hide();
  222. }
  223. }
  224. _canvas.observeJob('canvas.panel.iframe.load', function(panelDoc) {
  225. _canvas.fireJobs(Trex.Ev.__IFRAME_LOAD_COMPLETE, panelDoc);
  226. });
  227. },
  228. _isForceTextMode: function() {
  229. // 기존에는 아래의 조건이었으나 모바일에서의 호환은 아직 문제가 많아 제한함. 20140430
  230. // ($tx.ios && $tx.ios_ver < 5) || ($tx.android && $tx.android_ver < 3)
  231. return $tx.ios || $tx.android;
  232. },
  233. /**
  234. * Canvas의 mode를 바꾸는것으로, 현재 활성화되어있는 panel을 변경한다.
  235. * @param {String} newMode - 변경 할 mode에 해당하는 문자열
  236. * @example
  237. * editor.getCanvas().changeMode('html');
  238. * editor.getCanvas().changeMode('source');
  239. * editor.getCanvas().changeMode('text');
  240. */
  241. changeMode: function(newMode) {
  242. var _editor = this.editor;
  243. var oldMode = this.mode;
  244. if (oldMode == newMode) {
  245. return;
  246. }
  247. if (this._isForceTextMode() && oldMode == Trex.Canvas.__TEXT_MODE) {
  248. return;
  249. }
  250. var _oldPanel = this.panels[oldMode];
  251. var _newPanel = this.panels[newMode];
  252. if (!_oldPanel || !_newPanel) {
  253. throw new Error("[Exception]Trex.Canvas : not suppored mode");
  254. }
  255. var _oldContent = _oldPanel.getContent();
  256. var _content = _editor.getDocParser().getContentsAtChangingMode(_oldContent, oldMode, newMode);
  257. if (oldMode == Trex.Canvas.__WYSIWYG_MODE) { //NOTE: #FTDUEDTR-366
  258. if ($tx.msie_ver === 8) {
  259. _oldPanel.hide();
  260. } //prevent black screen from youtube iframe. #FTDUEDTR-1272
  261. _oldPanel.setContent("");
  262. try {
  263. this.focusOnTop();
  264. }catch(e){}
  265. }
  266. try { //#FTDUEDTR-1111
  267. _newPanel.setContent(_content);
  268. } catch (error) {
  269. alert(' - Error: ' + error.message + '\n에디터 타입 변경에 실패하였습니다.\n잘못된 HTML이 있는지 확인해주세요.');
  270. _oldPanel.setContent(_oldContent);
  271. _oldPanel.show();
  272. return;
  273. }
  274. this.mode = newMode;
  275. this.fireJobs(Trex.Ev.__CANVAS_MODE_CHANGE, oldMode, newMode);
  276. _newPanel.setPanelHeight(_oldPanel.getPanelHeight());
  277. _newPanel.show();
  278. _oldPanel.hide();
  279. // FF2 bug:: When display is none, designMode can't be set to on
  280. try {
  281. if (newMode == "html" && !this.getPanel("html").designModeActivated && $tx.gecko) {
  282. this.getPanel("html").el.contentDocument.designMode = "on";
  283. this.getPanel("html").designModeActivated = _TRUE;
  284. }
  285. } catch (e) {
  286. throw e;
  287. }
  288. },
  289. /**
  290. * 현재 panel에 포커스를 준다.
  291. */
  292. focus: function() {
  293. this.panels[this.mode].focus();
  294. },
  295. /**
  296. * 본문의 처음으로 캐럿을 옮긴다. - Only Wysiwyg
  297. */
  298. focusOnTop: function() {
  299. this.getProcessor().focusOnTop();
  300. },
  301. /**
  302. * 본문의 마지막으로 캐럿을 옮긴다. - Only Wysiwyg
  303. */
  304. focusOnBottom: function() {
  305. this.getProcessor().focusOnBottom();
  306. },
  307. /**
  308. * canvas의 position을 가져온다.
  309. * @returns {Object} position = { x: number, y:number }
  310. */
  311. getCanvasPos: function() {
  312. var _position = $tx.cumulativeOffset(this.elContainer);
  313. return {
  314. 'x': _position[0],
  315. 'y': _position[1]
  316. };
  317. },
  318. /**
  319. * canvas의 height를 변경한다.
  320. * @param {String} size (px)
  321. * @example
  322. * canvas.setCanvasSize({
  323. * height: "500px"
  324. * });
  325. */
  326. setCanvasSize: function(size) {
  327. if (this.panels[this.mode] && size.height) {
  328. this.panels[this.mode].setPanelHeight(size.height);
  329. } else {
  330. throw new Error("[Exception]Trex.Canvas : argument has no property - size.height ");
  331. }
  332. },
  333. /**
  334. * @Deprecated use isWYSIWYG()
  335. */
  336. canHTML: function() {
  337. return this.isWYSIWYG();
  338. },
  339. isWYSIWYG: function () {
  340. return this.mode === Trex.Canvas.__WYSIWYG_MODE;
  341. },
  342. /**
  343. * panel 객체를 가져온다.
  344. * @param {String} mode - 가져올 panel 모드명
  345. * @returns {Object} - parameter에 해당하는 Panel
  346. * @example
  347. * this.getPanel('html').designModeActivated = true;
  348. */
  349. getPanel: function(mode) {
  350. if (this.panels[mode]) {
  351. return this.panels[mode];
  352. } else {
  353. return _NULL;
  354. }
  355. },
  356. /**
  357. * 현재 활성화되어있는 panel 객체를 가져온다.
  358. * @returns {Object} - 활성화되어있는 panel 객체
  359. */
  360. getCurrentPanel: function() {
  361. if (this.panels[this.mode]) {
  362. return this.panels[this.mode];
  363. } else {
  364. return _NULL;
  365. }
  366. },
  367. /**
  368. * 현재 활성화되어있는 panel의 processor을 가져온다.
  369. * @returns {Object} - 활성화되어있는 panel의 processor 객체
  370. */
  371. getProcessor: function(mode) {
  372. if ( !mode ){
  373. return this.panels[this.mode].getProcessor();
  374. }else{
  375. return this.panels[mode].getProcessor();
  376. }
  377. },
  378. /**
  379. * 본문의 내용을 가져온다
  380. * @returns {String}
  381. */
  382. getContent: function() {
  383. var _content = this.panels[this.mode].getContent();
  384. if(_content) {
  385. _content = _content.replace(Trex.__WORD_JOINER_REGEXP, ""); //NOTE: 서비스의 DB charset이 euc-kr 계열일 경우 문제가 있음.
  386. }
  387. return _content;
  388. },
  389. /**
  390. * 현재 Wysiwyg 영역의 수직 스크롤 값을 얻어온다. - Only Wysiwyg
  391. * @function
  392. * @returns {Number} 수직 스크롤 값
  393. * @see Trex.Canvas.WysiwygPanel#getScrollTop
  394. */
  395. getScrollTop: function() {
  396. if(!this.isWYSIWYG()) {
  397. return 0;
  398. }
  399. return this.panels[this.mode].getScrollTop();
  400. },
  401. /**
  402. * Wysiwyg 영역의 수직 스크롤 값을 셋팅한다. - Only Wysiwyg
  403. * @function
  404. * @param {Number} scrollTop - 수직 스크롤 값
  405. * @see Trex.Canvas.WysiwygPanel#setScrollTop
  406. */
  407. setScrollTop: function(scrollTop) {
  408. if(!this.isWYSIWYG()) {
  409. return;
  410. }
  411. this.panels[this.mode].setScrollTop(scrollTop);
  412. },
  413. /**
  414. * 현재 활성화된 panel에 컨텐츠를 주어진 문자열로 수정한다.
  415. * @param {String} content - 컨텐츠
  416. */
  417. setContent: function(content) {
  418. this.panels[this.mode].setContent(content);
  419. this.includeWebfontCss(content);
  420. },
  421. /**
  422. * panel에 컨텐츠를 주어진 문자열로 초기화한다.
  423. * @param {String} content - 컨텐츠
  424. */
  425. initContent: function(content) {
  426. this.history.initHistory({
  427. 'content': content
  428. });
  429. this.panels[this.mode].setContent(content);
  430. this.includeWebfontCss(content);
  431. this.fireJobs(Trex.Ev.__CANVAS_DATA_INITIALIZE, Trex.Canvas.__WYSIWYG_MODE, _NULL);
  432. /* //NOTE: 메일은 수정이 없음. 답장 전달의 경우에는 본문 상단에 포커싱이 가도록.
  433. if ( $tx.gecko ){
  434. var me = this;
  435. setTimeout( function(){
  436. me.focusOnBottom();
  437. },500)
  438. }else{
  439. this.focusOnBottom();
  440. }
  441. */
  442. },
  443. /**
  444. * 컨텐츠를 파싱하여 사용되고 있는 웹폰트가 있으면, 웹폰트 css를 로딩한다. - Only Wysiwyg
  445. * @param {string} content
  446. * @see Trex.Canvas.WysiwygPanel#includeWebfontCss
  447. */
  448. includeWebfontCss: function(content) {
  449. if(!this.isWYSIWYG()) {
  450. return;
  451. }
  452. return this.panels[this.mode].includeWebfontCss(content);
  453. },
  454. /**
  455. * 본문에 사용된 웹폰트명 목록을 리턴한다. - Only Wysiwyg
  456. * @function
  457. * @returns {Array} 사용하고 있는 웹폰트명 목록
  458. * @see Trex.Canvas.WysiwygPanel#getUsedWebfont
  459. */
  460. getUsedWebfont: function() {
  461. if(!this.isWYSIWYG()) {
  462. return [];
  463. }
  464. return this.panels[this.mode].getUsedWebfont();
  465. },
  466. /**
  467. * 자바스크립트를 동적으로 실행한다 - Only Wysiwyg
  468. * @param {String} scripts - 자바스크립트 문자열
  469. */
  470. runScript: function(scripts) {
  471. if(!this.isWYSIWYG()) {
  472. return [];
  473. }
  474. this.panels[this.mode].runScript(scripts);
  475. },
  476. /**
  477. * 자바스크립트 소스를 로딩하여 동적으로 실행한다 - Only Wysiwyg
  478. * @param {String} url - 자바스크립트 url
  479. */
  480. importScript: function(url, callback) {
  481. if(!this.isWYSIWYG()) {
  482. return [];
  483. }
  484. this.panels[this.mode].importScript(url, callback);
  485. },
  486. /**
  487. * 선택된 영역의 상태 값을 알기위해 주어진 함수를 실행시킨다. - Only Wysiwyg
  488. * @param {Function} handler - 주어진 함수
  489. * @example
  490. * var _data = canvas.query(function(processor) {
  491. * return processor.queryCommandState('bold');
  492. * });
  493. */
  494. query: function(handler) {
  495. if(!this.isWYSIWYG()) {
  496. return _NULL;
  497. }
  498. var _processor = this.getProcessor();
  499. /* Block Scrolling
  500. if($tx.msie) {
  501. _processor.focus();
  502. }
  503. */
  504. return handler(_processor);
  505. },
  506. /**
  507. * 선택된 영역에 주어진 handler를 실행시킨다.
  508. * @param {Function} handler - 주어진 함수
  509. * @example
  510. * canvas.execute(function(processor) {
  511. * processor.execCommand('bold', _NULL);
  512. * });
  513. */
  514. execute: function(handler) {
  515. var _history = this.history;
  516. var _processor = this.getProcessor();
  517. if (this.isWYSIWYG()) {
  518. this.getPanel('html').ensureFocused();
  519. if (_processor.restoreRange) {
  520. setTimeout(function () { //NOTE: #FTDUEDTR-435
  521. _processor.restoreRange();
  522. handler(_processor);
  523. _history.saveHistory();
  524. _processor.restore();
  525. }, 0);
  526. } else {
  527. _processor.focus();
  528. handler(_processor);
  529. _history.saveHistory();
  530. _processor.restore();
  531. }
  532. } else {
  533. handler(_processor);
  534. }
  535. },
  536. /**
  537. * caret을 주어진 위치로 이동한다. - Only Wysiwyg <br/>
  538. * aaa.bbb - bbb라는 클래스를 가진 aaa 노드의 다음에 커서를 이동한다.
  539. * @param {String} scope
  540. */
  541. moveCaret: function(scope) {
  542. if(!scope) {
  543. return;
  544. }
  545. if(!this.isWYSIWYG()) {
  546. return;
  547. }
  548. this.getProcessor().moveCaretWith(scope);
  549. },
  550. /**
  551. * 선택한 영역에 HTML 컨텐츠를 삽입한다.
  552. * @param {String} content - 삽입하고자 하는 HTML 컨텐츠
  553. * @param {Boolean} newline - 현재 영역에서 한줄을 띄운 후 삽입할지 여부 true/_FALSE
  554. * @param {Object} wrapStyle - wrapper 노드에 적용할 스타일, <br/>
  555. * newline이 true 일 경우에만 의미를 갖는다.
  556. */
  557. pasteContent: function(content, newline, wrapStyle) {
  558. newline = newline || _FALSE;
  559. this.execute(function(processor) {
  560. processor.pasteContent(content, newline, wrapStyle);
  561. });
  562. },
  563. /**
  564. * 선택한 영역에 노드를 삽입한다. - Only Wysiwyg
  565. * @param {Array|Element} node - 삽입하고자 하는 노드 배열 또는 노드
  566. * @param {Boolean} newline - 현재 영역에서 한줄을 띄운 후 삽입할지 여부 true/_FALSE
  567. * @param {Object} wrapStyle - wrapper 노드에 적용할 스타일, <br/>
  568. * newline이 true 일 경우에만 의미를 갖는다.
  569. */
  570. pasteNode: function(node, newline, wrapStyle) {
  571. if (!this.isWYSIWYG()) {
  572. return;
  573. }
  574. newline = newline || _FALSE;
  575. this.execute(function(processor) {
  576. processor.pasteNode(node, newline, wrapStyle);
  577. });
  578. },
  579. /**
  580. * 현재 활성화된 panel에 스타일을 적용한다.
  581. * @param {Object} styles - 적용할 스타일
  582. */
  583. addStyle: function(styles) {
  584. this.panels[this.mode].addStyle(styles);
  585. },
  586. /**
  587. * 스타일명으로 현재 활성화된 panel의 스타일 값을 얻어온다.
  588. * @param {String} name - 스타일명
  589. * @returns {String} 해당 스타일 값
  590. */
  591. getStyle: function(name) {
  592. return this.panels[this.mode].getStyle(name);
  593. },
  594. /**
  595. * 특정 노드의 Wysiwyg 영역에서의 상대 위치를 얻어온다. - Only Wysiwyg
  596. * @function
  597. * @param {Element} node - 특정 노드
  598. * @returns {Object} position 객체 = {
  599. * x: number,
  600. * y: number,
  601. * width: number,
  602. * height: number
  603. * }
  604. */
  605. getPositionByNode: function(node) {
  606. if(!this.isWYSIWYG()) {
  607. return {
  608. x: 0,
  609. y: 0,
  610. width: 0,
  611. height: 0
  612. };
  613. }
  614. return this.panels[this.mode].getPositionByNode(node);
  615. },
  616. onKeyDown: function(event) {
  617. var p = this.getProcessor();
  618. var doc = this.getCurrentPanel().getDocument();
  619. function getNodeAndOffsetAtSel(){
  620. var rng = goog.dom.Range.createFromBrowserSelection(doc.getSelection? doc.getSelection():p.getSel());
  621. var node = rng.getStartNode();
  622. var offset = rng.getStartOffset();
  623. return {node: node,
  624. offset: offset}
  625. }
  626. var where = getNodeAndOffsetAtSel();
  627. this.fireJobs(Trex.Ev.__CANVAS_PANEL_KEYDOWN, event);
  628. var prev = null;
  629. if(event.keyCode == Trex.__KEY.BACKSPACE && p.isCollapsed() && (prev = $tom.prevNodeUntilTagName(where.node, where.offset, 'table')) && $tom.isTagName(prev, 'table')){
  630. $tx.stop(event);
  631. this.fireJobs(Trex.Ev.__CANVAS_PANEL_BACKSPACE_TABLE, prev);
  632. }
  633. if (this.config.useHotKey) {
  634. this.fireKeys(event);
  635. }
  636. },
  637. onKeyUp: function(event) {
  638. var keyCode = event.keyCode+'';
  639. if (shouldTriggerQuery(keyCode)) {
  640. this.getProcessor().clearDummy();
  641. }
  642. this.history.saveHistoryByKeyEvent(event);
  643. try {
  644. this.mayAttachmentChanged = _TRUE;
  645. this.fireJobs(Trex.Ev.__CANVAS_PANEL_KEYUP, event);
  646. if (this.isWYSIWYG() && shouldTriggerQuery(keyCode)) {
  647. this.triggerQueryStatus();
  648. }
  649. if (keyCode === Trex.__KEY.DELETE || keyCode === Trex.__KEY.BACKSPACE) { //NOTE: (Del/Backspace) keys를 눌러 본문에서 무엇인가가 삭제되었다고 생각될 경우 첨부들의 싱크를 확인한다.
  650. this.fireJobs(Trex.Ev.__CANVAS_PANEL_DELETE_SOMETHING);
  651. }
  652. } catch(ignore) {
  653. }
  654. },
  655. onMouseOver: function(event) {
  656. try {
  657. this.fireMouseover($tx.element(event));
  658. this.fireJobs(Trex.Ev.__CANVAS_PANEL_MOUSEOVER, event);
  659. } catch (ignore) {
  660. }
  661. },
  662. onMouseMove: function(event) {
  663. try {
  664. this.fireJobs(Trex.Ev.__CANVAS_PANEL_MOUSEMOVE, event);
  665. } catch (ignore) {
  666. }
  667. },
  668. onMouseOut: function(event) {
  669. try {
  670. this.fireJobs(Trex.Ev.__CANVAS_PANEL_MOUSEOUT, event);
  671. } catch (ignore) {
  672. }
  673. },
  674. onMouseDown: function(event) {
  675. this.getProcessor().clearDummy();
  676. try {
  677. this.fireElements($tx.element(event));
  678. } catch(ignore) {
  679. }
  680. this.fireJobs(Trex.Ev.__CANVAS_PANEL_MOUSEDOWN, event);
  681. var history = this.history;
  682. history.saveHistoryIfEdited();
  683. },
  684. onMouseUp: function(event) {
  685. try {
  686. var self = this;
  687. self.fireJobs(Trex.Ev.__CANVAS_PANEL_MOUSEUP, event);
  688. setTimeout(function() {
  689. var googRange = self.getProcessor().createGoogRange();
  690. if (googRange) {
  691. self.fireJobs(Trex.Ev.__CANVAS_PANEL_QUERY_STATUS, googRange);
  692. }
  693. }, 20);
  694. } catch(ignore) {
  695. }
  696. },
  697. mayAttachmentChanged: _FALSE,
  698. onClick: function(event) {
  699. this.fireJobs(Trex.Ev.__CANVAS_PANEL_CLICK, event);
  700. },
  701. onDoubleClick: function(event) {
  702. this.fireJobs(Trex.Ev.__CANVAS_PANEL_DBLCLICK, event);
  703. },
  704. onScroll: function(event) {
  705. this.fireJobs(Trex.Ev.__CANVAS_PANEL_SCROLLING, event);
  706. },
  707. onPaste: function(event) {
  708. this.fireJobs(Trex.Ev.__CANVAS_PANEL_PASTE, event);
  709. },
  710. // TODO rename query status 라는 말 말고 다른 말 없을까?
  711. triggerQueryStatus: function() {
  712. this.cancelReservedQueryStatusTrigger();
  713. this.reserveQueryStatusTrigger();
  714. },
  715. reserveQueryStatusTrigger: function() {
  716. var self = this;
  717. this.reservedQueryStatusTrigger = setTimeout(function() {
  718. var googRange = self.getProcessor().createGoogRange();
  719. if (googRange) {
  720. self.fireJobs(Trex.Ev.__CANVAS_PANEL_QUERY_STATUS, googRange);
  721. self.fireElements(self.getProcessor().getNode());
  722. }
  723. }, 20); // IE의 경우 canvas.execute 에서 setTimeout 처리 하기 때문에, execute 뒤에 부르는 syncProperty가 그 뒤에 실행되게 하려고 20ms 딜레이 준다....
  724. },
  725. cancelReservedQueryStatusTrigger: function() {
  726. if (this.reservedQueryStatusTrigger) {
  727. clearTimeout(this.reservedQueryStatusTrigger);
  728. }
  729. },
  730. /**
  731. * @depreacated use canvas.triggerQueryStatus();
  732. */
  733. syncProperty: function() {
  734. this.triggerQueryStatus();
  735. }
  736. });
  737. })(Trex);
  738. Trex.module("bind canvas events for close external menus",
  739. function(editor, toolbar, sidebar, canvas/*, config*/) {
  740. var _shouldCloseMenus = function () {
  741. editor.fireJobs(Trex.Ev.__SHOULD_CLOSE_MENUS);
  742. };
  743. canvas.observeJob(Trex.Ev.__CANVAS_PANEL_CLICK, _shouldCloseMenus);
  744. canvas.observeJob(Trex.Ev.__CANVAS_SOURCE_PANEL_CLICK, _shouldCloseMenus);
  745. canvas.observeJob(Trex.Ev.__CANVAS_TEXT_PANEL_CLICK, _shouldCloseMenus);
  746. }
  747. );
  748. Trex.module("make getter for 'iframeheight' and 'iframetop' size",
  749. function(editor, toolbar, sidebar, canvas/*, config*/) {
  750. var _iframeHeight = 0;
  751. var _iframeTop = 0;
  752. function resetIframeAttributes() {
  753. var _wysiwygPanel = canvas.getPanel(Trex.Canvas.__WYSIWYG_MODE);
  754. _iframeHeight = _wysiwygPanel.getPanelHeight().parsePx();
  755. var _position = $tom.getPosition(_wysiwygPanel.el);
  756. _iframeTop = _position.y;
  757. }
  758. // canvas resize
  759. canvas.observeJob(Trex.Ev.__CANVAS_HEIGHT_CHANGE, function(height) {
  760. resetIframeAttributes();
  761. });
  762. canvas.observeJob(Trex.Ev.__CANVAS_FULL_SCREEN_CHANGE, function() {
  763. resetIframeAttributes();
  764. });
  765. canvas.observeJob(Trex.Ev.__CANVAS_NORMAL_SCREEN_CHANGE, function() {
  766. resetIframeAttributes();
  767. });
  768. canvas.observeJob('canvas.apply.background', function() {
  769. resetIframeAttributes();
  770. });
  771. canvas.observeJob('canvas.apply.letterpaper', function() {
  772. resetIframeAttributes();
  773. });
  774. canvas.reserveJob(Trex.Ev.__IFRAME_LOAD_COMPLETE, function() {
  775. resetIframeAttributes();
  776. },300);
  777. // attachbox change ui
  778. var attachbox = editor.getAttachBox();
  779. attachbox.observeJob(Trex.Ev.__ATTACHBOX_FULLSCREEN_SHOW, function() {
  780. resetIframeAttributes();
  781. });
  782. attachbox.observeJob(Trex.Ev.__ATTACHBOX_FULLSCREEN_HIDE, function() {
  783. resetIframeAttributes();
  784. });
  785. // window resize
  786. $tx.observe(_WIN, 'resize', function(){
  787. resetIframeAttributes();
  788. });
  789. // create interface
  790. canvas.getIframeHeight = function(){
  791. return _iframeHeight;
  792. };
  793. canvas.getIframeTop = function(){
  794. return _iframeTop;
  795. };
  796. }
  797. );
  798. Trex.module("sync attachment data periodically", function(editor, toolbar, sidebar, canvas/*, config*/) {
  799. setTimeout(function() {
  800. setInterval(function() {
  801. if (canvas.mayAttachmentChanged) {
  802. // TODO 굳이 event 를 이용할 필요없이 바로 호출해줘도 될 것 같다.
  803. canvas.fireJobs(Trex.Ev.__CANVAS_PANEL_DELETE_SOMETHING);
  804. canvas.mayAttachmentChanged = _FALSE;
  805. }
  806. }, 3000);
  807. }, 10000);
  808. });
  809. Trex.module("synchronize the font style when caret is in end of paragraph", function(editor, toolbar, sidebar, canvas/*, config*/) {
  810. // only gecko #FTDUEDTR-1415
  811. $tx.gecko && canvas.observeJob(Trex.Ev.__CANVAS_PANEL_MOUSEUP, function(ev){
  812. if (canvas.isWYSIWYG()) {
  813. var clickEl = ev.target;
  814. var isParagraph = clickEl instanceof HTMLParagraphElement;
  815. var isHtml = clickEl instanceof HTMLHtmlElement;
  816. if (!isParagraph && !isHtml) {
  817. return;
  818. }
  819. var processor = canvas.getProcessor();
  820. var x = ev.pageX,
  821. y = ev.pageY;
  822. var caret = processor.doc.caretPositionFromPoint(x, y);
  823. var node = caret && caret.offsetNode;
  824. var des = node && $tom.descendants(node, '#text');
  825. if (!des || !des.length) {
  826. return;
  827. }
  828. var lastTextNode = des[des.length-1];
  829. if (lastTextNode) {
  830. var newRange = processor.createGoogRangeFromNodes(lastTextNode, lastTextNode.length, lastTextNode, lastTextNode.length);
  831. newRange.select();
  832. }
  833. }
  834. });
  835. });
  836. // FTDUEDTR-1431
  837. Trex.module("apply respectVisibilityInDesign for old IE", function(editor, toolbar, sidebar, canvas, config) {
  838. var isOldIE = ($tx.msie && ($tx.msie_docmode < 9));
  839. isOldIE && canvas.observeJob(Trex.Ev.__CANVAS_MODE_CHANGE, function(oldMode, newMode){
  840. changeVisibilityValue();
  841. });
  842. isOldIE && canvas.observeJob(Trex.Ev.__IFRAME_LOAD_COMPLETE, function(ev){
  843. changeVisibilityValue();
  844. });
  845. var COMMAND_API = 'RespectVisibilityInDesign';
  846. function changeVisibilityValue() {
  847. if (canvas.isWYSIWYG()) {
  848. var processor = canvas.getProcessor();
  849. var state = processor.doc.queryCommandState(COMMAND_API);
  850. var configFlag = canvas.config.respectVisibilityInDesign;
  851. if (state != configFlag) {
  852. processor.doc.execCommand(COMMAND_API, false, configFlag);
  853. console.log('RespectVisibilityInDesign ', configFlag);
  854. }
  855. }
  856. }
  857. });