/freetrix/js/fileman/html_editor/html-editor.js

https://github.com/ivanbogomoloff/open_bx · JavaScript · 4014 lines · 3788 code · 140 blank · 86 comment · 82 complexity · 3f1a64531ed549bfe9b5b952025610dd MD5 · raw file

  1. /**
  2. * Freetrix HTML Editor 3.0
  3. * Date: 24.04.13
  4. * Time: 4:23
  5. */
  6. (function() {
  7. var __BXHtmlEditorParserRules;
  8. function BXEditor(config)
  9. {
  10. // Container contains links to dom elements
  11. this.InitUtil();
  12. this.dom = {
  13. // cont -
  14. // iframeCont -
  15. // textareaCont -
  16. // iframe -
  17. // textarea -
  18. };
  19. this.bxTags = {};
  20. this.EMPTY_IMAGE_SRC = '/freetrix/images/1.gif';
  21. this.HTML5_TAGS = [
  22. "abbr", "article", "aside", "audio", "bdi", "canvas", "command", "datalist", "details", "figcaption",
  23. "figure", "footer", "header", "hgroup", "keygen", "mark", "meter", "nav", "output", "progress",
  24. "rp", "rt", "ruby", "svg", "section", "source", "summary", "time", "track", "video", "wbr"
  25. ];
  26. this.BLOCK_TAGS = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "BLOCKQUOTE", "DIV"];
  27. this.NESTED_BLOCK_TAGS = ["BLOCKQUOTE", "DIV"];
  28. this.HTML_ENTITIES = ['¡','¢','£','¤','¥','¦','§','¨','©','ª','«','¬','®','¯','°','±','²','³','´','µ','¶','·','¸','¹','º','»','¼','½','¾','¿','À','Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë','Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö','×','Ø','Ù','Ú','Û','Ü','Ý','Þ','ß','à','á','â','ã','ä','å','æ','ç','è','é','ê','ë','ì','í','î','ï','ð','ñ','ò','ó','ô','õ','ö','÷','ø','ù','ú','û','ü','ý','þ','ÿ','Œ','œ','Š','š','Ÿ','ˆ','˜','–','—','‘','’','‚','“','”','„','†','‡','‰','‹','›','€','Α','Β','Γ','Δ','Ε','Ζ','Η','Θ','Ι','Κ','Λ','Μ','Ν','Ξ','Ο','Π','Ρ','Σ','Τ','Υ','Φ','Χ','Ψ','Ω','α','β','γ','δ','ε','ζ','η','θ','ι','κ','λ','μ','ν','ξ','ο','π','ρ','ς','σ','τ','υ','φ','χ','ψ','ω','•','…','′','″','‾','⁄','™','←','↑','→','↓','↔','∂','∑','−','√','∞','∫','≈','≠','≡','≤','≥','◊','♠','♣','♥'];
  29. if(!BX.browser.IsIE())
  30. {
  31. this.HTML_ENTITIES = this.HTML_ENTITIES.concat(['ϑ','ϒ','ϖ','℘','ℑ','ℜ','ℵ','↵','⇐','⇑','⇒','⇓','⇔','∀','∃','∅','∇','∈','∉','∋','∏','∗','∝','∠','∧','∨','∩','∪','∴','∼','≅','⊂','⊃','⊄','⊆','⊇','⊕','⊗','⊥','⋅','⌈','⌉','⌊','⌋','⟨','⟩','♦']);
  32. }
  33. this.SHORTCUTS = {
  34. "66": "bold", // B
  35. "73": "italic", // I
  36. "85": "underline" // U
  37. };
  38. this.KEY_CODES = {
  39. 'backspace': 8,
  40. 'enter': 13,
  41. 'escape': 27,
  42. 'space': 32,
  43. 'delete': 46,
  44. 'left': 37,
  45. 'right': 39,
  46. 'up': 38,
  47. 'down': 40,
  48. 'z': 90,
  49. 'y': 89
  50. };
  51. this.INVISIBLE_SPACE = "\uFEFF";
  52. this.INVISIBLE_CURSOR = "\u2060";
  53. this.NORMAL_WIDTH = 1000;
  54. this.MIN_WIDTH = 700;
  55. this.MIN_HEIGHT = 400;
  56. this.MAX_HANDLED_FORMAT_LENGTH = 50000; // Max length of code which will be formated
  57. this.MAX_HANDLED_FORMAT_TIME = 500;
  58. config = this.CheckConfig(config);
  59. this.Init(config);
  60. }
  61. BXEditor.prototype = {
  62. Init: function(config)
  63. {
  64. this.On("OnEditorInitedBefore");
  65. this.config = config;
  66. this.id = this.config.id;
  67. this.dialogs = {};
  68. this.config.splitVertical = !!this.config.splitVertical;
  69. this.config.splitRatio = parseFloat(this.config.splitRatio);
  70. this.config.view = this.config.view || 'wysiwyg';
  71. this.config.taskbarShown = !!this.config.taskbarShown;
  72. this.config.taskbarWidth = parseInt(this.config.taskbarWidth);
  73. this.allowPhp = !!this.config.allowPhp;
  74. this.templateId = this.config.templateId;
  75. this.showSnippets = this.config.showSnippets !== false;
  76. this.showSnippets = false;
  77. this.showComponents = this.config.showComponents !== false && this.allowPhp;
  78. this.showTaskbars = this.showSnippets || this.showComponents;
  79. this.templates = {};
  80. this.templates[this.templateId] = this.config.templateParams;
  81. this.BuildSceleton();
  82. this.HTMLStyler = HTMLStyler;
  83. // Textarea
  84. this.dom.textarea = this.dom.textareaCont.appendChild(BX.create("TEXTAREA", {props: {className: "bxhtmled-textarea"}}));
  85. this.dom.pValueInput = BX('bxed_' + this.id);
  86. if (!this.dom.pValueInput)
  87. {
  88. this.dom.pValueInput = this.dom.cont.appendChild(BX.create("INPUT", {props: {type: "hidden", id: 'bxed_' + this.id, name: this.config.inputName, value: this.config.content}}));
  89. }
  90. this.dom.form = this.dom.textarea.form || false;
  91. this.document = null;
  92. // Protected iframe for wysiwyg
  93. this.sandbox = this.CreateIframeSandBox();
  94. var iframe = this.sandbox.GetIframe();
  95. iframe.style.width = '100%';
  96. iframe.style.height = '100%';
  97. // Views:
  98. // 1. TextareaView
  99. this.textareaView = new BXEditorTextareaView(this, this.dom.textarea, this.dom.textareaCont);
  100. // 2. IframeView
  101. this.iframeView = new BXEditorIframeView(this, this.dom.textarea, this.dom.iframeCont);
  102. // 3. Syncronizer
  103. this.synchro = new BXEditorViewsSynchro(this, this.textareaView, this.iframeView);
  104. // Parser
  105. this.parser = new BXHtmlEditor.BXEditorParser(this);
  106. // Php parser
  107. this.phpParser = new BXHtmlEditor.BXEditorPhpParser(this);
  108. this.components = new BXHtmlEditor.BXEditorComponents(this);
  109. // Toolbar
  110. this.overlay = new BXHtmlEditor.Overlay(this);
  111. this.BuildToolbar();
  112. // Taskbars
  113. if (this.showTaskbars)
  114. {
  115. this.taskbarManager = new BXHtmlEditor.TaskbarManager(this, true);
  116. // Components
  117. if (this.showComponents)
  118. {
  119. this.componentsTaskbar = new BXHtmlEditor.ComponentsControl(this);
  120. this.taskbarManager.AddTaskbar(this.componentsTaskbar);
  121. }
  122. // Snippets
  123. if (this.showSnippets)
  124. {
  125. this.snippetsTaskbar = new BXHtmlEditor.SnippetsControl(this, this.taskbarManager);
  126. this.taskbarManager.AddTaskbar(this.snippetsTaskbar);
  127. }
  128. this.taskbarManager.ShowTaskbar(this.showComponents ? this.componentsTaskbar.GetId() : this.snippetsTaskbar.GetId());
  129. }
  130. // Context menu
  131. this.contextMenu = new BXHtmlEditor.ContextMenu(this);
  132. this.nodeNavi = new BXHtmlEditor.NodeNavigator(this);
  133. this.nodeNavi.Show();
  134. this.styles = new BXStyles(this);
  135. this.InitEventHandlers();
  136. this.ResizeSceleton();
  137. // Restore taskbar mode from user settings
  138. if (this.showTaskbars && this.config.taskbarShown)
  139. {
  140. this.taskbarManager.Show(false);
  141. }
  142. this.inited = true;
  143. this.On("OnEditorInitedAfter");
  144. },
  145. InitEventHandlers: function()
  146. {
  147. var _this = this;
  148. BX.bind(this.dom.cont, 'click', BX.proxy(this.OnClick, this));
  149. BX.bind(this.dom.cont, 'mousedown', BX.proxy(this.OnMousedown, this));
  150. BX.bind(window, 'resize', function(){_this.ResizeSceleton();});
  151. if (BX.adminMenu)
  152. {
  153. BX.addCustomEvent(BX.adminMenu, 'onAdminMenuResize', function(){_this.ResizeSceleton();});
  154. }
  155. BX.addCustomEvent(this, "OnIframeFocus", function()
  156. {
  157. _this.bookmark = null;
  158. if (_this.statusInterval)
  159. {
  160. clearInterval(_this.statusInterval);
  161. }
  162. _this.statusInterval = setInterval(BX.proxy(_this.CheckCurrentStatus, _this), 500);
  163. });
  164. BX.addCustomEvent(this, "OnIframeBlur", function()
  165. {
  166. _this.bookmark = null;
  167. if (_this.statusInterval)
  168. {
  169. clearInterval(_this.statusInterval);
  170. }
  171. });
  172. BX.addCustomEvent(this, "OnTextareaFocus", function()
  173. {
  174. _this.bookmark = null;
  175. if (_this.statusInterval)
  176. {
  177. clearInterval(_this.statusInterval);
  178. }
  179. });
  180. // Surrogates
  181. BX.addCustomEvent(this, "OnSurrogateDblClick", function(bxTag, origTag, target, e)
  182. {
  183. if (origTag)
  184. {
  185. switch (origTag.tag)
  186. {
  187. case 'php':
  188. case 'javascript':
  189. case 'htmlcomment':
  190. case 'iframe':
  191. case 'style':
  192. _this.GetDialog('Source').Show(origTag);
  193. break;
  194. }
  195. }
  196. });
  197. if (this.dom.form)
  198. {
  199. BX.bind(this.dom.form, 'submit', BX.proxy(this.OnSubmit, this));
  200. }
  201. BX.addCustomEvent(this, "OnSpecialcharInserted", function(entity)
  202. {
  203. var lastChars = _this.GetLastSpecialchars();
  204. lastChars.unshift(entity);
  205. lastChars.pop();
  206. _this.config.lastSpecialchars = lastChars;
  207. _this.SaveOption('specialchars', lastChars.join('|'));
  208. });
  209. this.parentDialog = BX.WindowManager.Get();
  210. if (this.parentDialog && this.parentDialog.DIV && BX.isNodeInDom(this.parentDialog.DIV)
  211. &&
  212. BX.findParent(this.dom.cont, function(n){return n == _this.parentDialog.DIV;}))
  213. {
  214. BX.addCustomEvent(this.parentDialog, 'onWindowResizeExt', function(){_this.ResizeSceleton();});
  215. }
  216. },
  217. BuildSceleton: function()
  218. {
  219. // Main container contain all editor parts
  220. this.dom.cont = BX('bx-html-editor-' + this.id);
  221. this.dom.toolbarCont = BX('bx-html-editor-tlbr-cnt-' + this.id);
  222. this.dom.toolbar = BX('bx-html-editor-tlbr-' + this.id);
  223. this.dom.areaCont = BX('bx-html-editor-area-cnt-' + this.id);
  224. // Container for content editable iframe
  225. this.dom.iframeCont = BX('bx-html-editor-iframe-cnt-' + this.id);
  226. this.dom.textareaCont = BX('bx-html-editor-ta-cnt-' + this.id);
  227. this.dom.resizerOverlay = BX('bx-html-editor-res-over-' + this.id);
  228. this.dom.splitResizer = BX('bx-html-editor-split-resizer-' + this.id);
  229. this.dom.splitResizer.className = this.config.splitVertical ? "bxhtmled-split-resizer-ver" : "bxhtmled-split-resizer-hor";
  230. BX.bind(this.dom.splitResizer, 'mousedown', BX.proxy(this.StartSplitResize, this));
  231. // Taskbars
  232. this.dom.taskbarCont = BX('bx-html-editor-tskbr-cnt-' + this.id);
  233. // Node navigation at the bottom
  234. this.dom.navCont = BX('bx-html-editor-nav-cnt-' + this.id);
  235. },
  236. ResizeSceleton: function(width, height, params)
  237. {
  238. if (this.expanded)
  239. {
  240. var innerSize = BX.GetWindowInnerSize(document);
  241. width = this.config.width = innerSize.innerWidth;
  242. height = this.config.height = innerSize.innerHeight;
  243. }
  244. if (!width)
  245. {
  246. width = this.config.width;
  247. }
  248. if (!height)
  249. {
  250. height = this.config.height;
  251. }
  252. this.dom.cont.style.minWidth = this.MIN_WIDTH + 'px';
  253. this.dom.cont.style.minHeight = this.MIN_HEIGHT + 'px';
  254. var styleW, styleH;
  255. if (width.toString().indexOf('%') !== -1)
  256. {
  257. styleW = width;
  258. width = this.dom.cont.offsetWidth;
  259. }
  260. else
  261. {
  262. if (width < this.MIN_WIDTH)
  263. {
  264. width = this.MIN_WIDTH;
  265. }
  266. styleW = width + 'px';
  267. }
  268. this.dom.cont.style.width = styleW;
  269. this.dom.toolbarCont.style.width = styleW;
  270. if (height.toString().indexOf('%') !== -1)
  271. {
  272. styleH = height;
  273. height = this.dom.cont.offsetHeight;
  274. }
  275. else
  276. {
  277. if (height < this.MIN_HEIGHT)
  278. {
  279. height = this.MIN_HEIGHT;
  280. }
  281. styleH = height + 'px';
  282. }
  283. this.dom.cont.style.height = styleH;
  284. var
  285. w = Math.max(width, this.MIN_WIDTH),
  286. h = Math.max(height, this.MIN_HEIGHT),
  287. taskbarWidth = this.showTaskbars ? (this.taskbarManager.GetWidth(true, w * 0.8)) : 0,
  288. areaH = h - this.toolbar.GetHeight() - this.nodeNavi.GetHeight(),
  289. areaW = w - taskbarWidth;
  290. // Area
  291. this.SetAreaContSize(areaW, areaH, params);
  292. // Taskbars
  293. this.dom.taskbarCont.style.height = areaH + 'px';
  294. this.dom.taskbarCont.style.width = taskbarWidth + 'px';
  295. if (this.showTaskbars)
  296. {
  297. this.taskbarManager.Resize(taskbarWidth, areaH);
  298. }
  299. this.toolbar.AdaptControls(width);
  300. },
  301. SetAreaContSize: function(areaW, areaH, params)
  302. {
  303. this.dom.areaCont.style.width = areaW + 'px';
  304. this.dom.areaCont.style.height = areaH + 'px';
  305. if (params && params.areaContTop)
  306. {
  307. this.dom.areaCont.style.top = params.areaContTop + 'px';
  308. }
  309. var WIDTH_DIF = 2;
  310. if (this.currentViewName == 'split')
  311. {
  312. function getValue(value, min, max)
  313. {
  314. if (value < min)
  315. {
  316. value = min;
  317. }
  318. if (value > max)
  319. {
  320. value = max;
  321. }
  322. return value;
  323. }
  324. var MIN_SPLITTER_PAD = 10, delta, a, b;
  325. if (this.config.splitVertical == true)
  326. {
  327. delta = params && params.deltaX ? params.deltaX : 0;
  328. a = getValue((areaW * this.config.splitRatio / (1 + this.config.splitRatio)) - delta, MIN_SPLITTER_PAD, areaW - MIN_SPLITTER_PAD);
  329. b = areaW - a;
  330. this.dom.iframeCont.style.width = (a - WIDTH_DIF) + 'px';
  331. this.dom.iframeCont.style.height = areaH + 'px';
  332. this.dom.iframeCont.style.top = 0;
  333. this.dom.iframeCont.style.left = 0;
  334. this.dom.textareaCont.style.width = (b - WIDTH_DIF) + 'px';
  335. this.dom.textareaCont.style.height = areaH + 'px';
  336. this.dom.textareaCont.style.top = 0;
  337. this.dom.textareaCont.style.left = a + 'px';
  338. this.dom.splitResizer.className = 'bxhtmled-split-resizer-ver';
  339. this.dom.splitResizer.style.top = 0;
  340. this.dom.splitResizer.style.left = (a - 3) + 'px';
  341. this.dom.textareaCont.style.height = areaH + 'px';
  342. }
  343. else
  344. {
  345. delta = params && params.deltaY ? params.deltaY : 0;
  346. a = getValue((areaH * this.config.splitRatio / (1 + this.config.splitRatio)) - delta, MIN_SPLITTER_PAD, areaH - MIN_SPLITTER_PAD);
  347. b = areaH - a;
  348. this.dom.iframeCont.style.width = (areaW - WIDTH_DIF) + 'px';
  349. this.dom.iframeCont.style.height = a + 'px';
  350. this.dom.iframeCont.style.top = 0;
  351. this.dom.iframeCont.style.left = 0;
  352. this.dom.textareaCont.style.width = (areaW - WIDTH_DIF) + 'px';
  353. this.dom.textareaCont.style.height = b + 'px';
  354. this.dom.textareaCont.style.top = a + 'px';
  355. this.dom.textareaCont.style.left = 0;
  356. this.dom.splitResizer.className = 'bxhtmled-split-resizer-hor';
  357. this.dom.splitResizer.style.top = (a - 3) + 'px';
  358. this.dom.splitResizer.style.left = 0;
  359. }
  360. if (params && params.updateSplitRatio)
  361. {
  362. this.config.splitRatio = a / b;
  363. this.SaveOption('split_ratio', this.config.splitRatio);
  364. }
  365. }
  366. else
  367. {
  368. // Set size and position of iframe container to the normal state
  369. this.dom.iframeCont.style.width = (areaW - WIDTH_DIF) + 'px';
  370. this.dom.iframeCont.style.height = areaH + 'px';
  371. this.dom.iframeCont.style.top = 0;
  372. this.dom.iframeCont.style.left = 0;
  373. // Set size and position of textarea container to the normal state
  374. this.dom.textareaCont.style.width = (areaW - WIDTH_DIF) + 'px';
  375. this.dom.textareaCont.style.height = areaH + 'px';
  376. this.dom.textareaCont.style.top = 0;
  377. this.dom.textareaCont.style.left = 0;
  378. }
  379. },
  380. BuildToolbar: function()
  381. {
  382. this.toolbar = new BXHtmlEditor.Toolbar(this, this.GetTopControls());
  383. },
  384. GetTopControls: function()
  385. {
  386. this.On("GetTopButtons", [window.BXHtmlEditor.Controls]);
  387. return window.BXHtmlEditor.Controls;
  388. },
  389. CreateIframeSandBox: function()
  390. {
  391. return new Sandbox(
  392. // Callback
  393. BX.proxy(this.OnCreateIframe, this),
  394. // Config
  395. {
  396. editor: this,
  397. cont: this.dom.iframeCont
  398. }
  399. );
  400. },
  401. OnCreateIframe: function()
  402. {
  403. this.On('OnCreateIframeBefore');
  404. var _this = this;
  405. this.iframeView.OnCreateIframe();
  406. this.selection = new BXEditorSelection(this);
  407. this.action = new BXEditorActions(this);
  408. this.undoManager = new BXEditorUndoManager(this);
  409. this.config.content = this.dom.pValueInput.value;
  410. this.SetContent(this.config.content, true);
  411. this.action.Exec("styleWithCSS", false, true);
  412. this.iframeView.InitAutoLinking();
  413. // Simulate html5 placeholder attribute on contentEditable element
  414. // var placeholderText = typeof(this.config.placeholder) === "string"
  415. // ? this.config.placeholder
  416. // : this.textarea.element.getAttribute("placeholder");
  417. // if (placeholderText) {
  418. // dom.simulatePlaceholder(this.parent, this, placeholderText);
  419. // }
  420. //this._initObjectResizing();
  421. // Simulate html5 autofocus on contentEditable element
  422. // if (this.textarea.element.hasAttribute("autofocus") || document.querySelector(":focus") == this.textarea.element)
  423. // {
  424. // setTimeout(function() {
  425. // that.focus(); }, 100);
  426. // }
  427. // IE sometimes leaves a single paragraph, which can't be removed by the user
  428. // if (!browser.clearsContentEditableCorrectly())
  429. // {
  430. // ensureProperClearing(this);
  431. // }
  432. // if (!browser.clearsListsInContentEditableCorrectly())
  433. // {
  434. // ensureProperClearingOfLists(this);
  435. // }
  436. this.SetView(this.config.view, false);
  437. this.Focus(false);
  438. this.On('OnCreateIframeAfter');
  439. },
  440. GetDialog: function(dialogName, params)
  441. {
  442. if (!this.dialogs[dialogName] && window.BXHtmlEditor.dialogs[dialogName])
  443. this.dialogs[dialogName] = new window.BXHtmlEditor.dialogs[dialogName](this, params);
  444. return this.dialogs[dialogName] || null;
  445. },
  446. Show: function()
  447. {
  448. this.dom.cont.style.display = '';
  449. },
  450. Hide: function()
  451. {
  452. this.dom.cont.style.display = 'none';
  453. },
  454. IsShown: function()
  455. {
  456. return this.dom.cont.style.display !== 'none' && BX.isNodeInDom(this.dom.cont);
  457. },
  458. SetView: function(view, saveValue)
  459. {
  460. this.On('OnSetViewBefore');
  461. if (this.currentViewName != view)
  462. {
  463. if (view == 'wysiwyg')
  464. {
  465. this.iframeView.Show();
  466. this.textareaView.Hide();
  467. this.dom.splitResizer.style.display = 'none';
  468. }
  469. else if (view == 'code')
  470. {
  471. this.iframeView.Hide();
  472. this.textareaView.Show();
  473. this.dom.splitResizer.style.display = 'none';
  474. }
  475. else if (view == 'split')
  476. {
  477. this.textareaView.Show();
  478. this.iframeView.Show();
  479. this.dom.splitResizer.style.display = '';
  480. }
  481. this.currentViewName = view;
  482. }
  483. if (saveValue !== false)
  484. {
  485. this.SaveOption('view', view);
  486. }
  487. this.ResizeSceleton();
  488. this.On('OnSetViewAfter');
  489. },
  490. SetContent: function(value, bParse)
  491. {
  492. this.On('OnSetContentBefore');
  493. this.iframeView.SetValue(value, bParse);
  494. this.textareaView.SetValue(value, bParse);
  495. this.On('OnSetContentAfter');
  496. },
  497. Focus: function(setToEnd)
  498. {
  499. if (this.currentViewName == 'wysiwyg')
  500. {
  501. this.iframeView.Focus(setToEnd);
  502. }
  503. else if (this.currentViewName == 'code')
  504. {
  505. this.textareaView.Focus(setToEnd);
  506. }
  507. else if (this.currentViewName == 'split')
  508. {
  509. if (this.synchro.GetSplitMode() == 'wysiwyg')
  510. {
  511. this.iframeView.Focus(setToEnd);
  512. }
  513. else
  514. {
  515. this.textareaView.Focus(setToEnd);
  516. }
  517. }
  518. this.On('OnFocus');
  519. return this;
  520. },
  521. LoadContent: function()
  522. {
  523. },
  524. SaveContent: function()
  525. {
  526. if (this.currentViewName == 'wysiwyg' ||
  527. (this.currentViewName == 'split' && this.synchro.GetSplitMode() == 'wysiwyg'))
  528. {
  529. this.lastIframeValue = '';
  530. this.synchro.FromIframeToTextarea(true);
  531. }
  532. else
  533. {
  534. this.textareaView.SaveValue();
  535. }
  536. },
  537. GetContent: function()
  538. {
  539. this.SaveContent();
  540. return this.textareaView.GetValue();
  541. },
  542. IsExpanded: function()
  543. {
  544. return this.expanded;
  545. },
  546. Expand: function(bExpand)
  547. {
  548. if (bExpand == undefined)
  549. {
  550. bExpand = !this.expanded;
  551. }
  552. var
  553. _this = this,
  554. innerSize = BX.GetWindowInnerSize(document),
  555. startWidth, startHeight, startTop, startLeft,
  556. endWidth, endHeight, endTop, endLeft;
  557. if (bExpand)
  558. {
  559. var
  560. scrollPos = BX.GetWindowScrollPos(document),
  561. pos = BX.pos(this.dom.cont);
  562. startWidth = this.dom.cont.offsetWidth;
  563. startHeight = this.dom.cont.offsetHeight;
  564. startTop = pos.top;
  565. startLeft = pos.left;
  566. endWidth = innerSize.innerWidth;
  567. endHeight = innerSize.innerHeight;
  568. endTop = scrollPos.scrollTop;
  569. endLeft = scrollPos.scrollLeft;
  570. this.savedSize = {
  571. width: startWidth,
  572. height: startHeight,
  573. top: startTop,
  574. left: startLeft,
  575. scrollLeft: scrollPos.scrollLeft,
  576. scrollTop: scrollPos.scrollTop,
  577. configWidth: this.config.width,
  578. configHeight: this.config.height
  579. };
  580. this.config.width = endWidth;
  581. this.config.height = endHeight;
  582. BX.addClass(this.dom.cont, 'bx-html-editor-absolute');
  583. this._bodyOverflow = document.body.style.overflow;
  584. document.body.style.overflow = "hidden";
  585. //window.scrollTo(0, 0);
  586. // Create dummie div
  587. this.dummieDiv = BX.create('DIV');
  588. this.dummieDiv.style.width = startWidth + 'px';
  589. this.dummieDiv.style.height = startHeight + 'px';
  590. this.dom.cont.parentNode.insertBefore(this.dummieDiv, this.dom.cont);
  591. document.body.appendChild(this.dom.cont);
  592. BX.addCustomEvent(this, 'OnIframeKeydown', BX.proxy(this.CheckEscCollapse, this));
  593. BX.bind(document.body, "keydown", BX.proxy(this.CheckEscCollapse, this));
  594. }
  595. else
  596. {
  597. startWidth = this.dom.cont.offsetWidth;
  598. startHeight = this.dom.cont.offsetHeight;
  599. startTop = this.savedSize.scrollTop;
  600. startLeft = this.savedSize.scrollLeft;
  601. endWidth = this.savedSize.width;
  602. endHeight = this.savedSize.height;
  603. endTop = this.savedSize.top;
  604. endLeft = this.savedSize.left;
  605. BX.removeCustomEvent(this, 'OnIframeKeydown', BX.proxy(this.CheckEscCollapse, this));
  606. BX.unbind(document.body, "keydown", BX.proxy(this.CheckEscCollapse, this));
  607. }
  608. this.dom.cont.style.width = startWidth + 'px';
  609. this.dom.cont.style.height = startHeight + 'px';
  610. this.dom.cont.style.top = startTop + 'px';
  611. this.dom.cont.style.left = startLeft + 'px';
  612. this.expandAnimation = new BX.easing({
  613. duration : 300,
  614. start : {
  615. height: startHeight,
  616. width: startWidth,
  617. top: startTop,
  618. left: startLeft
  619. },
  620. finish : {
  621. height: endHeight,
  622. width: endWidth,
  623. top: endTop,
  624. left: endLeft
  625. },
  626. transition : BX.easing.makeEaseOut(BX.easing.transitions.quart),
  627. step : function(state)
  628. {
  629. _this.dom.cont.style.width = state.width + 'px';
  630. _this.dom.cont.style.height = state.height + 'px';
  631. _this.dom.cont.style.top = state.top + 'px';
  632. _this.dom.cont.style.left = state.left + 'px';
  633. _this.ResizeSceleton(state.width.toString(), state.height.toString());
  634. },
  635. complete : function()
  636. {
  637. _this.expandAnimation = null;
  638. if (bExpand)
  639. {
  640. //BX.bind(window, 'resize', BX.proxy(_this.ResizeExpanded, _this));
  641. }
  642. else
  643. {
  644. _this.util.ReplaceNode(_this.dummieDiv, _this.dom.cont);
  645. _this.dummieDiv = null;
  646. _this.dom.cont.style.width = '';
  647. _this.dom.cont.style.height = '';
  648. _this.dom.cont.style.top = '';
  649. _this.dom.cont.style.left = '';
  650. BX.removeClass(_this.dom.cont, 'bx-html-editor-absolute');
  651. document.body.style.overflow = _this._bodyOverflow;
  652. _this.config.width = _this.savedSize.configWidth;
  653. _this.config.height = _this.savedSize.configHeight;
  654. //window.scrollTo(_this.savedSize.scrollLeft, _this.savedSize.scrollTop);
  655. _this.ResizeSceleton();
  656. //BX.unbind(window, 'resize', BX.proxy(_this.ResizeExpanded, _this));
  657. }
  658. BX.defer(_this.ReInitIframe, _this)();
  659. }
  660. });
  661. this.expandAnimation.animate();
  662. this.expanded = bExpand;
  663. },
  664. CheckEscCollapse: function(e, keyCode, command, selectedNode)
  665. {
  666. if (!keyCode)
  667. {
  668. keyCode = e.keyCode;
  669. }
  670. if (
  671. this.IsExpanded() &&
  672. keyCode == this.KEY_CODES['escape'] &&
  673. !this.IsPopupsOpened()
  674. )
  675. {
  676. this.Expand(false);
  677. return BX.PreventDefault(e);
  678. }
  679. },
  680. IsPopupsOpened: function()
  681. {
  682. return !!(this.dialogShown ||
  683. this.popupShown ||
  684. this.contextMenuShown ||
  685. this.overlay.bShown);
  686. },
  687. ReInitIframe: function()
  688. {
  689. this.sandbox.InitIframe();
  690. this.iframeView.OnCreateIframe();
  691. this.synchro.StopSync();
  692. this.synchro.lastTextareaValue = '';
  693. this.synchro.FromTextareaToIframe(true);
  694. this.synchro.StartSync();
  695. this.iframeView.ReInit();
  696. this.Focus();
  697. },
  698. Disable: function()
  699. {
  700. },
  701. Enable: function()
  702. {
  703. },
  704. CheckConfig: function(config)
  705. {
  706. if (config.content === undefined)
  707. {
  708. config.content = '';
  709. }
  710. return config;
  711. },
  712. GetInnerHtml: function(el)
  713. {
  714. var
  715. TILDA = "%7E",
  716. AMP = "&amp;",
  717. innerHTML = el.innerHTML;
  718. if (innerHTML.indexOf(AMP) !== -1 || innerHTML.indexOf(TILDA) !== -1)
  719. {
  720. innerHTML = innerHTML.replace(/(?:href|src)\s*=\s*("|')([\s\S]*?)(\1)/ig, function(s)
  721. {
  722. // See https://bugzilla.mozilla.org/show_bug.cgi?id=664398
  723. s = s.replace(/%7E/ig, '~');
  724. s = s.replace(/&amp;/ig, '&');
  725. return s;
  726. });
  727. }
  728. return innerHTML;
  729. },
  730. InitUtil: function()
  731. {
  732. var _this = this;
  733. this.util = {};
  734. if ("textContent" in document.documentElement)
  735. {
  736. this.util.SetTextContent = function(element, text){element.textContent = text;};
  737. this.util.GetTextContent = function(element){return element.textContent;};
  738. }
  739. else if ("innerText" in document.documentElement)
  740. {
  741. this.util.SetTextContent = function(element, text){element.innerText = text;};
  742. this.util.GetTextContent = function(element){return element.innerText;};
  743. }
  744. else
  745. {
  746. this.util.SetTextContent = function(element, text){element.nodeValue = text;};
  747. this.util.GetTextContent = function(element){return element.nodeValue;};
  748. }
  749. this.util.AutoCloseTagSupported = function()
  750. {
  751. var
  752. element = document.createElement("div"),
  753. result,
  754. innerHTML;
  755. element.innerHTML = "<p><div></div>";
  756. innerHTML = element.innerHTML.toLowerCase();
  757. result = innerHTML === "<p></p><div></div>" || innerHTML === "<p><div></div></p>";
  758. _this.util.AutoCloseTagSupported = function(){return result;};
  759. return result;
  760. };
  761. // IE sometimes gives wrong results for hasAttribute/getAttribute
  762. this.util.CheckGetAttributeTruth = function()
  763. {
  764. var
  765. td = document.createElement("td"),
  766. result = td.getAttribute("rowspan") != "1";
  767. _this.util.CheckGetAttributeTruth = function(){return result;};
  768. return result;
  769. };
  770. // Check if browser supports HTML5
  771. this.util.CheckHTML5Support = function(doc)
  772. {
  773. if (!doc)
  774. {
  775. doc = document;
  776. }
  777. var
  778. result = false,
  779. html = "<article>freetrix</article>",
  780. el = doc.createElement("div");
  781. el.innerHTML = html;
  782. result = el.innerHTML.toLowerCase() === html;
  783. _this.util.CheckHTML5Support = function(){return result;};
  784. return result;
  785. };
  786. // Check if browser supports HTML5 (checks all tags)
  787. this.util.CheckHTML5FullSupport = function(doc)
  788. {
  789. if (!doc)
  790. {
  791. doc = document;
  792. }
  793. var
  794. html,
  795. tags = _this.GetHTML5Tags(),
  796. result = false,
  797. el = doc.createElement("div");
  798. for (var i = 0; i < tags.length; i++)
  799. {
  800. html = "<" + tags[i] + ">freetrix</" + tags[i] + ">";
  801. el.innerHTML = html;
  802. result = el.innerHTML.toLowerCase() === html;
  803. if (!result)
  804. {
  805. break;
  806. }
  807. }
  808. _this.util.CheckHTML5FullSupport = function(){return result;};
  809. return result;
  810. };
  811. this.util.GetEmptyImage = function()
  812. {
  813. return _this.EMPTY_IMAGE_SRC;
  814. };
  815. this.util.CheckDataTransferSupport = function()
  816. {
  817. var result = false;
  818. try {
  819. result = !!(window.Clipboard || window.DataTransfer).prototype.getData;
  820. } catch(e) {}
  821. _this.util.CheckDataTransferSupport = function(){return result;};
  822. return result;
  823. };
  824. this.util.CheckImageSelectSupport = function()
  825. {
  826. var result = !(BX.browser.IsChrome() || BX.browser.IsSafari());
  827. _this.util.CheckImageSelectSupport = function(){return result;};
  828. return result;
  829. };
  830. // Following hack is needed for firefox to make sure that image resize handles are properly removed
  831. this.util.Refresh = function(element)
  832. {
  833. if (element && element.parentNode)
  834. {
  835. var cn = "bx-editor-refresh";
  836. BX.addClass(element, cn);
  837. BX.removeClass(element, cn);
  838. // Hack for firefox
  839. if (BX.browser.IsFirefox())
  840. {
  841. try {
  842. var
  843. i,
  844. doc = element.ownerDocument,
  845. italics = doc.getElementsByTagName('I'),
  846. italicLen = italics.length;
  847. for (i = 0; i < italics.length; i++)
  848. {
  849. italics[i].setAttribute('data-bx-orgig-i', true);
  850. }
  851. doc.execCommand("italic", false, null);
  852. doc.execCommand("italic", false, null);
  853. var italicsNew = doc.getElementsByTagName('I');
  854. if (italicsNew.length !== italicLen)
  855. {
  856. for (i = 0; i < italicsNew.length; i++)
  857. {
  858. if (italicsNew[i].getAttribute('data-bx-orgig-i'))
  859. {
  860. italicsNew[i].removeAttribute('data-bx-orgig-i');
  861. }
  862. else
  863. {
  864. _this.util.ReplaceWithOwnChildren(italicsNew[i]);
  865. }
  866. }
  867. }
  868. } catch(e) {}
  869. }
  870. }
  871. };
  872. this.util.addslashes = function(str)
  873. {
  874. str = str.replace(/\\/g,'\\\\');
  875. str = str.replace(/"/g,'\\"');
  876. return str;
  877. };
  878. this.util.stripslashes = function(str)
  879. {
  880. str = str.replace(/\\"/g,'"');
  881. str = str.replace(/\\\\/g,'\\');
  882. return str;
  883. };
  884. this.util.ReplaceNode = function(node, newNode)
  885. {
  886. node.parentNode.insertBefore(newNode, node);
  887. node.parentNode.removeChild(node);
  888. return newNode;
  889. };
  890. // Fast way to check whether an element with a specific tag name is in the given document
  891. this.util.DocumentHasTag = function(doc, tag)
  892. {
  893. var
  894. LIVE_CACHE = {},
  895. key = _this.id + ":" + tag,
  896. cacheEntry = LIVE_CACHE[key];
  897. if (!cacheEntry)
  898. cacheEntry = LIVE_CACHE[key] = doc.getElementsByTagName(tag);
  899. return cacheEntry.length > 0;
  900. };
  901. this.util.IsSplitPoint = function(node, offset)
  902. {
  903. var res = offset > 0 && offset < node.childNodes.length;
  904. if (rangy.dom.isCharacterDataNode(node))
  905. {
  906. if (offset == 0)
  907. res = !!node.previousSibling;
  908. else if (offset == node.length)
  909. res = !!node.nextSibling;
  910. else
  911. res = true;
  912. }
  913. return res;
  914. };
  915. this.util.SplitNodeAt = function(node, descendantNode, descendantOffset)
  916. {
  917. var newNode;
  918. if (rangy.dom.isCharacterDataNode(descendantNode))
  919. {
  920. if (descendantOffset == 0)
  921. {
  922. descendantOffset = rangy.dom.getNodeIndex(descendantNode);
  923. descendantNode = descendantNode.parentNode;
  924. }
  925. else if (descendantOffset == descendantNode.length)
  926. {
  927. descendantOffset = rangy.dom.getNodeIndex(descendantNode) + 1;
  928. descendantNode = descendantNode.parentNode;
  929. }
  930. else
  931. {
  932. newNode = rangy.dom.splitDataNode(descendantNode, descendantOffset);
  933. }
  934. }
  935. if (!newNode)
  936. {
  937. newNode = descendantNode.cloneNode(false);
  938. if (newNode.id)
  939. {
  940. newNode.removeAttribute("id");
  941. }
  942. var child;
  943. while ((child = descendantNode.childNodes[descendantOffset]))
  944. {
  945. newNode.appendChild(child);
  946. }
  947. rangy.dom.insertAfter(newNode, descendantNode);
  948. }
  949. if (descendantNode && descendantNode.nodeName == "BODY")
  950. {
  951. return newNode;
  952. }
  953. return (descendantNode == node) ? newNode : _this.util.SplitNodeAt(node, newNode.parentNode, rangy.dom.getNodeIndex(newNode));
  954. };
  955. this.util.ReplaceWithOwnChildren = function (el)
  956. {
  957. var parent = el.parentNode;
  958. while (el.firstChild)
  959. {
  960. parent.insertBefore(el.firstChild, el);
  961. }
  962. parent.removeChild(el);
  963. };
  964. this.util.IsBlockElement = function (node)
  965. {
  966. var styleDisplay = BX.style(node, 'display');
  967. return styleDisplay && styleDisplay.toLowerCase() === "block";
  968. };
  969. this.util.IsBlockNode = function (node)
  970. {
  971. return node && node.nodeType == 1 && BX.util.in_array(node.nodeName, _this.GetBlockTags());
  972. };
  973. this.util.CopyAttributes = function(attributes, from, to)
  974. {
  975. if (from && to)
  976. {
  977. var
  978. attribute,
  979. i,
  980. length = attributes.length;
  981. for (i = 0; i < length; i++)
  982. {
  983. attribute = attributes[i];
  984. if (from[attribute])
  985. to[attribute] = from[attribute];
  986. }
  987. }
  988. };
  989. this.util.RenameNode = function(node, newNodeName)
  990. {
  991. var
  992. newNode = node.ownerDocument.createElement(newNodeName),
  993. firstChild;
  994. while (firstChild = node.firstChild)
  995. newNode.appendChild(firstChild);
  996. _this.util.CopyAttributes(["align", "className"], node, newNode);
  997. if (node.style.cssText != '')
  998. {
  999. newNode.style.cssText = node.style.cssText;
  1000. }
  1001. node.parentNode.replaceChild(newNode, node);
  1002. return newNode;
  1003. };
  1004. this.util.GetInvisibleTextNode = function()
  1005. {
  1006. return _this.iframeView.document.createTextNode(_this.INVISIBLE_SPACE);
  1007. };
  1008. this.util.IsEmptyNode = function(node, bCheckNewLine, bCheckSpaces)
  1009. {
  1010. var res;
  1011. if (node.nodeType == 3)
  1012. {
  1013. res = node.data === "" || node.data === _this.INVISIBLE_SPACE || (node.data === '\n' && bCheckNewLine);
  1014. if (!res && bCheckSpaces && node.data.toString().match(/^[\s\n\r\t]+$/ig))
  1015. {
  1016. res = true;
  1017. }
  1018. }
  1019. else if(node.nodeType == 1)
  1020. {
  1021. res = node.innerHTML === "" || node.innerHTML === _this.INVISIBLE_SPACE;
  1022. if (!res && bCheckSpaces && node.innerHTML.toString().match(/^[\s\n\r\t]+$/ig))
  1023. {
  1024. res = true;
  1025. }
  1026. }
  1027. return res;
  1028. };
  1029. var documentElement = document.documentElement;
  1030. if ("textContent" in documentElement)
  1031. {
  1032. this.util.SetTextContent = function(node, text)
  1033. {
  1034. node.textContent = text;
  1035. };
  1036. this.util.GetTextContent = function(node)
  1037. {
  1038. return node.textContent;
  1039. };
  1040. }
  1041. else if ("innerText" in documentElement)
  1042. {
  1043. this.util.SetTextContent = function(node, text)
  1044. {
  1045. node.innerText = text;
  1046. };
  1047. this.util.GetTextContent = function(node)
  1048. {
  1049. return node.innerText;
  1050. };
  1051. }
  1052. else
  1053. {
  1054. this.util.SetTextContent = function(node, text)
  1055. {
  1056. node.nodeValue = text;
  1057. };
  1058. this.util.GetTextContent = function(node)
  1059. {
  1060. return node.nodeValue;
  1061. };
  1062. }
  1063. this.util.RgbToHex = function(str)
  1064. {
  1065. var res;
  1066. if (str.search("rgb") == -1)
  1067. {
  1068. res = str;
  1069. }
  1070. else if (str == 'rgba(0, 0, 0, 0)')
  1071. {
  1072. res = 'transparent';
  1073. }
  1074. else
  1075. {
  1076. str = str.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))?\)$/);
  1077. function hex(x)
  1078. {
  1079. return ("0" + parseInt(x).toString(16)).slice(-2);
  1080. }
  1081. res = "#" + hex(str[1]) + hex(str[2]) + hex(str[3]);
  1082. }
  1083. return res;
  1084. };
  1085. this.util.CheckCss = function(node, arCss, bMatch)
  1086. {
  1087. var res = true, i;
  1088. for (i in arCss)
  1089. {
  1090. if (arCss.hasOwnProperty(i))
  1091. {
  1092. if (node.style[i] != '')
  1093. {
  1094. res = res && (bMatch ? node.style[i] == arCss[i] : true);
  1095. }
  1096. else
  1097. {
  1098. res = false;
  1099. }
  1100. }
  1101. }
  1102. return res;
  1103. };
  1104. this.util.SetCss = function(n, arCss)
  1105. {
  1106. if (n && arCss && typeof arCss == 'object')
  1107. {
  1108. for (var i in arCss)
  1109. {
  1110. if (arCss.hasOwnProperty(i))
  1111. {
  1112. n.style[i] = arCss[i];
  1113. }
  1114. }
  1115. }
  1116. };
  1117. this.util.InsertAfter = function(node, precedingNode)
  1118. {
  1119. return rangy.dom.insertAfter(node, precedingNode);
  1120. };
  1121. this.util.GetNodeDomOffset = function(node)
  1122. {
  1123. var i = 0;
  1124. while (node.parentNode && node.parentNode.nodeName !== 'BODY')
  1125. {
  1126. node = node.parentNode;
  1127. i++;
  1128. }
  1129. return i;
  1130. };
  1131. this.util.CheckSurrogateNode = function(node)
  1132. {
  1133. return _this.phpParser.CheckParentSurrogate(node);
  1134. };
  1135. },
  1136. Parse: function(content, bParseBxNodes, bFormat)
  1137. {
  1138. this.On("OnParse", [bParseBxNodes]);
  1139. bParseBxNodes = !!bParseBxNodes;
  1140. if (bParseBxNodes)
  1141. {
  1142. content = this.parser.Parse(content, this.GetParseRules(), this.GetIframeDoc(), true, bParseBxNodes);
  1143. if (bFormat === true || this.textareaView.IsShown())
  1144. {
  1145. content = this.FormatHtml(content);
  1146. }
  1147. content = this.phpParser.ParseBxNodes(content);
  1148. }
  1149. else
  1150. {
  1151. content = this.phpParser.ParsePhp(content);
  1152. content = this.parser.Parse(content, this.GetParseRules(), this.GetIframeDoc(), true, bParseBxNodes);
  1153. }
  1154. return content;
  1155. },
  1156. On: function(eventName, arEventParams)
  1157. {
  1158. BX.onCustomEvent(this, eventName, arEventParams || []);
  1159. },
  1160. GetIframeDoc: function()
  1161. {
  1162. if (!this.document)
  1163. {
  1164. this.document = this.sandbox.GetDocument();
  1165. BX.addCustomEvent(this, 'OnIframeReInit', BX.proxy(function(){this.document = this.sandbox.GetDocument();}, this));
  1166. }
  1167. return this.document;
  1168. },
  1169. GetParseRules: function()
  1170. {
  1171. this.rules = __BXHtmlEditorParserRules;
  1172. // Here we can make changes to this.rules
  1173. this.On("OnGetParseRules");
  1174. var _this = this;
  1175. this.GetParseRules = function(){return _this.rules;};
  1176. return this.rules;
  1177. },
  1178. GetHTML5Tags: function()
  1179. {
  1180. return this.HTML5_TAGS;
  1181. },
  1182. GetBlockTags: function()
  1183. {
  1184. return this.BLOCK_TAGS;
  1185. },
  1186. SetBxTag: function(el, params)
  1187. {
  1188. var id;
  1189. if (params.id || el && el.id)
  1190. id = params.id || el.id;
  1191. if (!id)
  1192. {
  1193. id = 'bxid' + Math.round(Math.random() * 1000000000);
  1194. }
  1195. else
  1196. {
  1197. if (this.bxTags[id])
  1198. {
  1199. if (!params.tag)
  1200. params.tag = this.bxTags[id].tag;
  1201. }
  1202. }
  1203. params.id = id;
  1204. if (el)
  1205. el.id = params.id;
  1206. this.bxTags[params.id] = params;
  1207. return params.id;
  1208. },
  1209. GetBxTag: function(element)
  1210. {
  1211. var id;
  1212. if (typeof element == 'object' && element && element.id)
  1213. id = element.id;
  1214. else
  1215. id = element;
  1216. if (id)
  1217. {
  1218. if (typeof id != "string" && id.id)
  1219. id = id.id;
  1220. if (id && id.length > 0 && this.bxTags[id] && this.bxTags[id].tag)
  1221. {
  1222. this.bxTags[id].tag = this.bxTags[id].tag.toLowerCase();
  1223. return this.bxTags[id];
  1224. }
  1225. }
  1226. return {tag: false};
  1227. },
  1228. OnMousedown: function(e)
  1229. {
  1230. var
  1231. node = e.target || e.srcElement;
  1232. if (node && (node.getAttribute || node.parentNode))
  1233. {
  1234. var
  1235. _this = this,
  1236. bxType = node.getAttribute('data-bx-type');
  1237. if (!bxType)
  1238. {
  1239. node = BX.findParent(node, function(n)
  1240. {
  1241. return n == _this.dom.cont || (n.getAttribute && n.getAttribute('data-bx-type'));
  1242. }, this.dom.cont);
  1243. bxType = (node && node.getAttribute) ? node.getAttribute('data-bx-type') : null;
  1244. }
  1245. if (bxType == 'action') // Any type of button or element which runs action
  1246. {
  1247. return BX.PreventDefault(e);
  1248. }
  1249. }
  1250. return true;
  1251. },
  1252. OnClick: function(e)
  1253. {
  1254. var
  1255. target = e.target || e.srcElement,
  1256. bxType = (target && target.getAttribute) ? target.getAttribute('data-bx-type') : false;
  1257. this.On("OnClickBefore", [{e: e, target: target, bxType: bxType}]);
  1258. this.CheckCommand(target);
  1259. },
  1260. CheckCommand: function(node)
  1261. {
  1262. if (node && (node.getAttribute || node.parentNode))
  1263. {
  1264. var
  1265. _this = this,
  1266. bxType = node.getAttribute('data-bx-type');
  1267. if (!bxType)
  1268. {
  1269. node = BX.findParent(node, function(n)
  1270. {
  1271. return n == _this.dom.cont || (n.getAttribute && n.getAttribute('data-bx-type'));
  1272. }, this.dom.cont);
  1273. bxType = (node && node.getAttribute) ? node.getAttribute('data-bx-type') : null;
  1274. }
  1275. this.On("OnCommandBefore", [{node: node, bxType: bxType}]);
  1276. if (bxType == 'action') // Any type of button or element which runs action
  1277. {
  1278. var
  1279. action = node.getAttribute('data-bx-action'),
  1280. value = node.getAttribute('data-bx-value');
  1281. if (this.action.IsSupported(action))
  1282. {
  1283. this.action.Exec(action, value);
  1284. }
  1285. }
  1286. }
  1287. },
  1288. SetSplitMode: function(vertical, saveValue)
  1289. {
  1290. this.config.splitVertical = !!vertical;
  1291. if (saveValue !== false)
  1292. {
  1293. this.SaveOption('split_vertical', this.config.splitVertical ? 1 : 0);
  1294. }
  1295. this.SetView('split', saveValue);
  1296. },
  1297. GetSplitMode: function()
  1298. {
  1299. return this.config.splitVertical;
  1300. },
  1301. StartSplitResize: function(e)
  1302. {
  1303. this.dom.resizerOverlay.style.display = 'block';
  1304. var
  1305. dX = 0, dY = 0,
  1306. windowScroll = BX.GetWindowScrollPos(),
  1307. startX = e.clientX + windowScroll.scrollLeft,
  1308. startY = e.clientY + windowScroll.scrollTop,
  1309. _this = this;
  1310. function moveResizer(e, bFinish)
  1311. {
  1312. var
  1313. x = e.clientX + windowScroll.scrollLeft,
  1314. y = e.clientY + windowScroll.scrollTop;
  1315. if(startX == x && startY == y)
  1316. {
  1317. return;
  1318. }
  1319. dX = startX - x;
  1320. dY = startY - y;
  1321. _this.ResizeSceleton(0, 0, {deltaX: dX, deltaY: dY, updateSplitRatio: bFinish});
  1322. }
  1323. function finishResizing(e)
  1324. {
  1325. moveResizer(e, true);
  1326. BX.unbind(document, 'mousemove', moveResizer);
  1327. BX.unbind(document, 'mouseup', finishResizing);
  1328. _this.dom.resizerOverlay.style.display = 'none';
  1329. }
  1330. BX.bind(document, 'mousemove', moveResizer);
  1331. BX.bind(document, 'mouseup', finishResizing);
  1332. },
  1333. Request: function(P)
  1334. {
  1335. if (!P.url)
  1336. P.url = this.config.actionUrl;
  1337. if (P.bIter !== false)
  1338. P.bIter = true;
  1339. if (!P.postData && !P.getData)
  1340. P.getData = this.GetReqData();
  1341. // var errorText;
  1342. // if (!P.errorText)
  1343. // errorText = false;
  1344. var reqId = P.getData ? P.getData.reqId : P.postData.reqId;
  1345. var _this = this, iter = 0;
  1346. var handler = function(result)
  1347. {
  1348. function handleRes()
  1349. {
  1350. //_this.CloseWaitWindow();
  1351. // var erInd = result.toLowerCase().indexOf('bx_event_calendar_action_error');
  1352. // if (!result || result.length <= 0 || erInd != -1)
  1353. // {
  1354. // var errorText = '';
  1355. // if (erInd >= 0)
  1356. // {
  1357. // var
  1358. // ind1 = erInd + 'FX_EVENT_CALENDAR_ACTION_ERROR:'.length,
  1359. // ind2 = result.indexOf('-->', ind1);
  1360. // errorText = result.substr(ind1, ind2 - ind1);
  1361. // }
  1362. // if (P.onerror && typeof P.onerror == 'function')
  1363. // P.onerror();
  1364. //
  1365. // return _this.DisplayError(errorText || P.errorText || '');
  1366. // }
  1367. var res = P.handler(_this.GetRequestRes(reqId), result);
  1368. if(res === false && ++iter < 20 && P.bIter)
  1369. setTimeout(handleRes, 5);
  1370. else
  1371. _this.ClearRequestRes(reqId);
  1372. }
  1373. setTimeout(handleRes, 50);
  1374. };
  1375. //this.ShowWaitWindow();
  1376. if (P.postData)
  1377. BX.ajax.post(P.url, P.postData, handler);
  1378. else
  1379. BX.ajax.get(P.url, P.getData, handler);
  1380. },
  1381. GetRequestRes: function(key)
  1382. {
  1383. if (top.BXHtmlEditor && top.BXHtmlEditor.ajaxResponse[key] != undefined)
  1384. return top.BXHtmlEditor.ajaxResponse[key];
  1385. return {};
  1386. },
  1387. ClearRequestRes: function(key)
  1388. {
  1389. if (top.BXHtmlEditor.ajaxResponse)
  1390. {
  1391. top.BXHtmlEditor.ajaxResponse[key] = null;
  1392. delete top.BXHtmlEditor.ajaxResponse[key];
  1393. }
  1394. },
  1395. GetReqData: function(action, O)
  1396. {
  1397. if (!O)
  1398. O = {};
  1399. if (action)
  1400. O.action = action;
  1401. O.sessid = BX.freetrix_sessid();
  1402. O.bx_html_editor_request = 'Y';
  1403. O.reqId = Math.round(Math.random() * 1000000);
  1404. return O;
  1405. },
  1406. GetTemplateId: function()
  1407. {
  1408. return this.templateId;
  1409. },
  1410. GetTemplateParams: function()
  1411. {
  1412. return this.templates[this.templateId];
  1413. },
  1414. GetTemplateStyles: function()
  1415. {
  1416. var params = this.templates[this.templateId] || {};
  1417. return params.STYLES || '';
  1418. },
  1419. ApplyTemplate: function(templateId)
  1420. {
  1421. if (this.templateId !== templateId)
  1422. {
  1423. if (this.templates[templateId])
  1424. {
  1425. this.templateId = templateId;
  1426. var
  1427. i,
  1428. doc = this.sandbox.GetDocument(),
  1429. head = doc.head || doc.getElementsByTagName('HEAD')[0],
  1430. styles = head.getElementsByTagName('STYLE');
  1431. // Clean old template styles
  1432. for (i = 0; i < styles.length; i++)
  1433. {
  1434. if (styles[i].getAttribute('data-bx-template-style') == 'Y')
  1435. BX.cleanNode(styles[i], true);
  1436. }
  1437. // Add new node in the iframe head
  1438. if (this.templates[templateId]['STYLES'])
  1439. {
  1440. head.appendChild(BX.create('STYLE', {props: {type: 'text/css'}, text: this.templates[templateId]['STYLES']}, doc)).setAttribute('data-bx-template-style', 'Y');
  1441. }
  1442. this.On("OnApplySiteTemplate", [templateId]);
  1443. }
  1444. else
  1445. {
  1446. var _this = this;
  1447. this.Request({
  1448. getData: this.GetReqData('load_site_template',
  1449. {
  1450. site_template: templateId
  1451. }
  1452. ),
  1453. handler: function(res)
  1454. {
  1455. _this.templates[templateId] = res;
  1456. _this.ApplyTemplate(templateId);
  1457. }
  1458. });
  1459. }
  1460. }
  1461. },
  1462. FormatHtml: function(html, bForceFormating)
  1463. {
  1464. if (html.length < this.MAX_HANDLED_FORMAT_LENGTH || bForceFormating === true)
  1465. {
  1466. if (!this.formatter)
  1467. this.formatter = new window.BXHtmlEditor.BXCodeFormatter(this);
  1468. var time1 = new Date().getTime();
  1469. html = this.formatter.Format(html);
  1470. var time2 = new Date().getTime();
  1471. if (time2 - time1 > this.MAX_HANDLED_FORMAT_TIME)
  1472. this.MAX_HANDLED_FORMAT_LENGTH -= 5000;
  1473. }
  1474. return html;
  1475. },
  1476. GetFontFamilyList: function()
  1477. {
  1478. if (!this.fontFamilyList)
  1479. {
  1480. this.fontFamilyList = [
  1481. {value: ['Times New Roman', 'Times'], name: 'Times New Roman'},
  1482. {value: ['Courier New'], name: 'Courier New'},
  1483. {value: ['Arial', 'Helvetica'], name: 'Arial / Helvetica'},
  1484. {value: ['Arial Black', 'Gadget'], name: 'Arial Black'},
  1485. {value: ['Tahoma','Geneva'], name: 'Tahoma / Geneva'},
  1486. {value: 'Verdana', name: 'Verdana'},
  1487. {value: ['Georgia', 'serif'], name: 'Georgia'},
  1488. {value: 'monospace', name: 'monospace'}
  1489. ];
  1490. this.On("GetFontFamilyList", [this.fontFamilyList]);
  1491. }
  1492. return this.fontFamilyList;
  1493. },
  1494. GetStyleList: function()
  1495. {
  1496. if (!this.styleList)
  1497. {
  1498. this.styleList = [
  1499. {value: 'H1', tagName: 'H1', name: BX.message('StyleH1')},
  1500. {value: 'H2', tagName: 'H2', name: BX.message('StyleH2')},
  1501. {value: 'H3', tagName: 'H3', name: BX.message('StyleH3')},
  1502. {value: 'H4', tagName: 'H4', name: BX.message('StyleH4')},
  1503. {value: 'H5', tagName: 'H5', name: BX.message('StyleH5')},
  1504. {value: 'H6', tagName: 'H6', name: BX.message('StyleH6')},
  1505. {value: 'P', name: BX.message('StyleParagraph')},
  1506. {value: 'DIV', name: BX.message('StyleDiv')}
  1507. ];
  1508. this.On("GetStyleList", [this.styleList]);
  1509. }
  1510. return this.styleList;
  1511. },
  1512. CheckCurrentStatus: function()
  1513. {
  1514. var
  1515. arAction, action, actionState, value,
  1516. actionList = this.GetActiveActions();
  1517. for (action in actionList)
  1518. {
  1519. if (actionList.hasOwnProperty(action) && this.action.IsSupported(action))
  1520. {
  1521. arAction = actionList[action];
  1522. actionState = this.action.CheckState(action, arAction.value);
  1523. value = arAction.control.GetValue();
  1524. if (actionState)
  1525. {
  1526. arAction.control.SetValue(true, actionState, action);
  1527. }
  1528. else
  1529. {
  1530. arAction.control.SetValue(false, null, action);
  1531. }
  1532. }
  1533. }
  1534. },
  1535. RegisterCheckableAction: function(action, params)
  1536. {
  1537. if (!this.checkedActionList)
  1538. this.checkedActionList = {};
  1539. this.checkedActionList[action] = params;
  1540. },
  1541. GetActiveActions: function()
  1542. {
  1543. return this.checkedActionList;
  1544. },
  1545. SaveOption: function(name, value)
  1546. {
  1547. BX.userOptions.save('html_editor', 'user_settings', name, value);
  1548. },
  1549. GetCurrentCssClasses: function(filterTag)
  1550. {
  1551. return this.styles.GetCSS(this.templateId, this.templates[this.templateId].STYLES, this.templates[this.templateId].PATH || '', filterTag);
  1552. },
  1553. IsInited: function()
  1554. {
  1555. return !!this.inited;
  1556. },
  1557. IsContentChanged: function()
  1558. {
  1559. var
  1560. cont1 = this.config.content.replace(/[\s\n\r\t]+/ig, ''),
  1561. cont2 = this.GetContent().replace(/[\s\n\r\t]+/ig, '');
  1562. return cont1 != cont2;
  1563. },
  1564. IsSubmited: function()
  1565. {
  1566. return this.isSubmited;
  1567. },
  1568. OnSubmit: function()
  1569. {
  1570. if (!this.isSubmited)
  1571. {
  1572. this.isSubmited = true;
  1573. this.On('OnSubmit');
  1574. this.SaveContent();
  1575. }
  1576. },
  1577. Destroy: function()
  1578. {
  1579. this.sandbox.Destroy();
  1580. BX.remove(this.dom.cont);
  1581. },
  1582. GetLastSpecialchars: function()
  1583. {
  1584. var def = ['&cent;', '&sect;', '&euro;', '&pound;','&yen;','&copy;', '&reg;', '&laquo;', '&raquo;', '&deg;', '&plusmn;', '&para;', '&hellip;','&prime;','&Prime;', '&trade;', '&asymp;', '&ne;', '&lt;', '&gt;'];
  1585. if (this.config.lastSpecialchars && typeof this.config.lastSpecialchars == 'object' && this.config.lastSpecialchars.length > 1)
  1586. {
  1587. return this.config.lastSpecialchars;
  1588. }
  1589. else
  1590. {
  1591. return def;
  1592. }
  1593. },
  1594. GetIframeElement: function(id)
  1595. {
  1596. return this.GetIframeDoc().getElementById(id);
  1597. },
  1598. RegisterDialog: function(id, dialog)
  1599. {
  1600. window.BXHtmlEditor.dialogs[id] = dialog;
  1601. },
  1602. SetConfigHeight: function(height)
  1603. {
  1604. this.config.height = height;
  1605. if (this.IsExpanded())
  1606. {
  1607. this.savedSize.configHeight = height;
  1608. this.savedSize.height = height;
  1609. }
  1610. }
  1611. };
  1612. window.BXEditor = BXEditor;
  1613. function Sandbox(callBack, config)
  1614. {
  1615. this.callback = callBack || BX.DoNothing;
  1616. this.config = config || {};
  1617. this.editor = this.config.editor;
  1618. this.iframe = this.CreateIframe();
  1619. this.bSandbox = false;
  1620. // Properties to unset/protect on the window object
  1621. this.windowProperties = ["parent", "top", "opener", "frameElement", "frames",
  1622. "localStorage", "globalStorage", "sessionStorage", "indexedDB"];
  1623. //Properties on the window object which are set to an empty function
  1624. this.windowProperties2 = ["open", "close", "openDialog", "showModalDialog", "alert", "confirm", "prompt", "openDatabase", "postMessage", "XMLHttpRequest", "XDomainRequest"];
  1625. //Properties to unset/protect on the document object
  1626. this.documentProperties = ["referrer", "write", "open", "close"];
  1627. }
  1628. Sandbox.prototype =
  1629. {
  1630. GetIframe: function()
  1631. {
  1632. return this.iframe;
  1633. },
  1634. GetWindow: function()
  1635. {
  1636. this._readyError();
  1637. },
  1638. GetDocument: function()
  1639. {
  1640. this._readyError();
  1641. },
  1642. Destroy: function()
  1643. {
  1644. var iframe = this.GetIframe();
  1645. iframe.parentNode.removeChild(iframe);
  1646. },
  1647. _readyError: function()
  1648. {
  1649. throw new Error("Sandbox: Sandbox iframe isn't loaded yet");
  1650. },
  1651. CreateIframe: function()
  1652. {
  1653. var
  1654. _this = this,
  1655. iframe = BX.create("IFRAME", {
  1656. props: {
  1657. className: "bx-editor-iframe",
  1658. frameborder: 0,
  1659. allowtransparency: "true",
  1660. width: 0,
  1661. height: 0,
  1662. marginwidth: 0,
  1663. marginheight: 0
  1664. }
  1665. });
  1666. iframe.onload = function()
  1667. {
  1668. iframe.onreadystatechange = iframe.onload = null;
  1669. _this.OnLoadIframe(iframe);
  1670. };
  1671. iframe.onreadystatechange = function()
  1672. {
  1673. if (/loaded|complete/.test(iframe.readyState))
  1674. {
  1675. iframe.onreadystatechange = iframe.onload = null;
  1676. _this.OnLoadIframe(iframe);
  1677. }
  1678. };
  1679. // Append iframe to ext container
  1680. this.config.cont.appendChild(iframe);
  1681. return iframe;
  1682. },
  1683. OnLoadIframe: function(iframe)
  1684. {
  1685. if (BX.isNodeInDom(iframe))
  1686. {
  1687. var
  1688. _this = this,
  1689. iframeWindow = iframe.contentWindow,
  1690. iframeDocument = iframeWindow.document;
  1691. this.InitIframe(iframe);
  1692. // Catch js errors and pass them to the parent's onerror event
  1693. // addEventListener("error") doesn't work properly in some browsers
  1694. iframeWindow.onerror = function(errorMessage, fileName, lineNumber) {
  1695. throw new Error("Sandbox: " + errorMessage, fileName, lineNumber);
  1696. };
  1697. if (this.bSandbox)
  1698. {
  1699. // Unset a bunch of sensitive variables
  1700. // Please note: This isn't hack safe!
  1701. // It more or less just takes care of basic attacks and prevents accidental theft of sensitive information
  1702. // IE is secure though, which is the most important thing, since IE is the only browser, who
  1703. // takes over scripts & styles into contentEditable elements when copied from external websites
  1704. // or applications (Microsoft Word, ...)
  1705. var i, length;
  1706. for (i = 0, length = this.windowProperties.length; i < length; i++)
  1707. {
  1708. this._unset(iframeWindow, this.windowProperties[i]);
  1709. }
  1710. for (i=0, length = this.windowProperties2.length; i < length; i++)
  1711. {
  1712. this._unset(iframeWindow, this.windowProperties2[i], BX.DoNothing());
  1713. }
  1714. for (i = 0, length = this.documentProperties.length; i < length; i++)
  1715. {
  1716. this._unset(iframeDocument, this.documentProperties[i]);
  1717. }
  1718. // This doesn't work in Safari 5
  1719. // See http://stackoverflow.com/questions/992461/is-it-possible-to-override-document-cookie-in-webkit
  1720. this._unset(iframeDocument, "cookie", "", true);
  1721. }
  1722. this.loaded = true;
  1723. // Trigger the callback
  1724. setTimeout(function() {_this.callback(_this); }, 0);
  1725. }
  1726. },
  1727. InitIframe: function(iframe)
  1728. {
  1729. var
  1730. iframe = this.iframe || iframe,
  1731. iframeDocument = iframe.contentWindow.document,
  1732. iframeHtml = this.GetHtml(this.config.stylesheets, this.editor.GetTemplateStyles());
  1733. // Create the basic dom tree including proper DOCTYPE and charset
  1734. iframeDocument.open("text/html", "replace");
  1735. iframeDocument.write(iframeHtml);
  1736. iframeDocument.close();
  1737. this.GetWindow = function()
  1738. {
  1739. return iframe.contentWindow;
  1740. };
  1741. this.GetDocument = function()
  1742. {
  1743. return iframe.contentWindow.document;
  1744. };
  1745. },
  1746. GetHtml: function(css, cssText)
  1747. {
  1748. var
  1749. bodyParams = '',
  1750. headHtml = "",
  1751. i;
  1752. css = typeof css === "string" ? [css] : css;
  1753. if (css)
  1754. {
  1755. for (i = 0; i < css.length; i++)
  1756. headHtml += '<link rel="stylesheet" href="' + css[i] + '">';
  1757. }
  1758. if (this.config.bodyClass)
  1759. {
  1760. bodyParams += ' class="' + this.config.bodyClass + '"';
  1761. }
  1762. if (this.config.bodyId)
  1763. {
  1764. bodyParams += ' id="' + this.config.bodyId + '"';
  1765. }
  1766. if (typeof cssText === "string")
  1767. headHtml += '<style type="text/css" data-bx-template-style="Y">' + cssText + '</style>';
  1768. headHtml += '<link rel="stylesheet" href="' + this.editor.config.cssIframePath + '">';
  1769. return '<!DOCTYPE html><html><head>' + headHtml + '</head><body' + bodyParams + '></body></html>';
  1770. },
  1771. /**
  1772. * Method to unset/override existing variables
  1773. * @example
  1774. * // Make cookie unreadable and unwritable
  1775. * this._unset(document, "cookie", "", true);
  1776. */
  1777. _unset: function(object, property, value, setter)
  1778. {
  1779. try { object[property] = value; } catch(e) {}
  1780. try { object.__defineGetter__(property, function() { return value; }); } catch(e) {}
  1781. if (setter) {
  1782. try { object.__defineSetter__(property, function() {}); } catch(e) {}
  1783. }
  1784. if (!crashesWhenDefineProperty(property))
  1785. {
  1786. try {
  1787. var config = {
  1788. get: function() { return value; }
  1789. };
  1790. if (setter) {
  1791. config.set = function() {};
  1792. }
  1793. Object.defineProperty(object, property, config);
  1794. } catch(e) {}
  1795. }
  1796. }
  1797. };
  1798. function BXEditorSelection(editor)
  1799. {
  1800. this.editor = editor;
  1801. this.document = editor.sandbox.GetDocument();
  1802. BX.addCustomEvent(this.editor, 'OnIframeReInit', BX.proxy(function(){this.document = this.editor.sandbox.GetDocument();}, this));
  1803. // Make sure that our external range library is initialized
  1804. window.rangy.init();
  1805. }
  1806. BXEditorSelection.prototype =
  1807. {
  1808. // Get the current selection as a bookmark to be able to later restore it
  1809. GetBookmark: function()
  1810. {
  1811. if (this.editor.currentViewName !== 'code')
  1812. {
  1813. var range = this.GetRange();
  1814. return range && range.cloneRange();
  1815. }
  1816. return false;
  1817. },
  1818. // Restore a selection
  1819. SetBookmark: function(bookmark)
  1820. {
  1821. if (bookmark && this.editor.currentViewName !== 'code')
  1822. {
  1823. this.SetSelection(bookmark);
  1824. }
  1825. },
  1826. // Save current selection
  1827. SaveBookmark: function()
  1828. {
  1829. this.lastRange = this.GetBookmark();
  1830. return this.lastRange;
  1831. },
  1832. // Save current selection
  1833. RestoreBookmark: function()
  1834. {
  1835. if (this.lastRange)
  1836. {
  1837. this.SetBookmark(this.lastRange);
  1838. this.lastRange = false;
  1839. }
  1840. },
  1841. /**
  1842. * Set the caret in front of the given node
  1843. * @param {Object} node The element or text node where to position the caret in front of
  1844. */
  1845. SetBefore: function(node)
  1846. {
  1847. var range = rangy.createRange(this.document);
  1848. range.setStartBefore(node);
  1849. range.setEndBefore(node);
  1850. return this.SetSelection(range);
  1851. },
  1852. /**
  1853. * Set the caret after the given node
  1854. *
  1855. * @param {Object} node The element or text node where to position the caret in front of
  1856. */
  1857. SetAfter: function(node)
  1858. {
  1859. var range = rangy.createRange(this.document);
  1860. range.setStartAfter(node);
  1861. range.setEndAfter(node);
  1862. return this.SetSelection(range);
  1863. },
  1864. /**
  1865. * Ability to select/mark nodes
  1866. *
  1867. * @param {Element} node The node/element to select
  1868. */
  1869. SelectNode: function(node)
  1870. {
  1871. var
  1872. range = rangy.createRange(this.document),
  1873. isElement = node.nodeType === 1,
  1874. canHaveHTML = "canHaveHTML" in node ? node.canHaveHTML : (node.nodeName !== "IMG"),
  1875. content = isElement ? node.innerHTML : node.data,
  1876. isEmpty = (content === "" || content === this.editor.INVISIBLE_SPACE),
  1877. styleDisplay = BX.style(node, 'display'),
  1878. bBlock = (styleDisplay === "block" || styleDisplay === "list-item");
  1879. if (isEmpty && isElement && canHaveHTML)
  1880. {
  1881. // Make sure that caret is visible in node by inserting a zero width no breaking space
  1882. try {
  1883. node.innerHTML = this.editor.INVISIBLE_SPACE;
  1884. } catch(e) {}
  1885. }
  1886. if (canHaveHTML)
  1887. range.selectNodeContents(node);
  1888. else
  1889. range.selectNode(node);
  1890. if (canHaveHTML && isEmpty && isElement)
  1891. {
  1892. range.collapse(bBlock);
  1893. }
  1894. else if (canHaveHTML && isEmpty)
  1895. {
  1896. range.setStartAfter(node);
  1897. range.setEndAfter(node);
  1898. }
  1899. this.SetSelection(range);
  1900. return range;
  1901. },
  1902. /**
  1903. * Get the node which contains the selection
  1904. *
  1905. * @param {Boolean} [controlRange] (only IE) Whether it should return the selected ControlRange element when the selection type is a "ControlRange"
  1906. * @return {Object} The node that contains the caret
  1907. */
  1908. GetSelectedNode: function(controlRange)
  1909. {
  1910. var
  1911. res,
  1912. selection,
  1913. range;
  1914. if (controlRange && this.document.selection && this.document.selection.type === "Control")
  1915. {
  1916. range = this.document.selection.createRange();
  1917. if (range && range.length)
  1918. {
  1919. res = range.item(0);
  1920. }
  1921. }
  1922. if (!res)
  1923. {
  1924. selection = this.GetSelection();
  1925. if (selection.focusNode === selection.anchorNode)
  1926. {
  1927. res = selection.focusNode;
  1928. }
  1929. }
  1930. if (!res)
  1931. {
  1932. range = this.GetRange();
  1933. res = range ? range.commonparentContainer : this.document.body
  1934. }
  1935. if (res && res.ownerDocument != this.editor.GetIframeDoc())
  1936. {
  1937. res = this.document.body;
  1938. }
  1939. return res;
  1940. },
  1941. ExecuteAndRestore: function(method, restoreScrollPosition)
  1942. {
  1943. var
  1944. body = this.document.body,
  1945. oldScrollTop = restoreScrollPosition && body.scrollTop,
  1946. oldScrollLeft = restoreScrollPosition && body.scrollLeft,
  1947. className = "_bx-editor-temp-placeholder",
  1948. placeholderHTML = '<span class="' + className + '">' + this.editor.INVISIBLE_SPACE + '</span>',
  1949. range = this.GetRange(),
  1950. newRange;
  1951. // Nothing selected, execute and say goodbye
  1952. if (!range)
  1953. {
  1954. method(body, body);
  1955. return;
  1956. }
  1957. var node = range.createContextualFragment(placeholderHTML);
  1958. range.insertNode(node);
  1959. // Make sure that a potential error doesn't cause our placeholder element to be left as a placeholder
  1960. try
  1961. {
  1962. method(range.startContainer, range.endContainer);
  1963. }
  1964. catch(e3)
  1965. {
  1966. setTimeout(function() { throw e3; }, 0);
  1967. }
  1968. var caretPlaceholder = this.document.querySelector("." + className);
  1969. if (caretPlaceholder)
  1970. {
  1971. newRange = rangy.createRange(this.document);
  1972. newRange.selectNode(caretPlaceholder);
  1973. newRange.deleteContents();
  1974. this.SetSelection(newRange);
  1975. }
  1976. else
  1977. {
  1978. // fallback for when all hell breaks loose
  1979. body.focus();
  1980. }
  1981. if (restoreScrollPosition)
  1982. {
  1983. body.scrollTop = oldScrollTop;
  1984. body.scrollLeft = oldScrollLeft;
  1985. }
  1986. // Remove it again, just to make sure that the placeholder is definitely out of the dom tree
  1987. try {
  1988. if (caretPlaceholder.parentNode)
  1989. caretPlaceholder.parentNode.removeChild(caretPlaceholder);
  1990. } catch(e4) {}
  1991. },
  1992. /**
  1993. * Different approach of preserving the selection (doesn't modify the dom)
  1994. * Takes all text nodes in the selection and saves the selection position in the first and last one
  1995. */
  1996. ExecuteAndRestoreSimple: function(method)
  1997. {
  1998. var
  1999. range = this.GetRange(),
  2000. body = this.document.body,
  2001. newRange,
  2002. firstNode,
  2003. lastNode,
  2004. textNodes,
  2005. rangeBackup;
  2006. // Nothing selected, execute and say goodbye
  2007. if (!range)
  2008. {
  2009. method(body, body);
  2010. return;
  2011. }
  2012. textNodes = range.getNodes([3]);
  2013. firstNode = textNodes[0] || range.startContainer;
  2014. lastNode = textNodes[textNodes.length - 1] || range.endContainer;
  2015. rangeBackup = {
  2016. collapsed: range.collapsed,
  2017. startContainer: firstNode,
  2018. startOffset: firstNode === range.startContainer ? range.startOffset : 0,
  2019. endContainer: lastNode,
  2020. endOffset: lastNode === range.endContainer ? range.endOffset : lastNode.length
  2021. };
  2022. try
  2023. {
  2024. method(range.startContainer, range.endContainer);
  2025. }
  2026. catch(e)
  2027. {
  2028. setTimeout(function() { throw e; }, 0);
  2029. }
  2030. newRange = rangy.createRange(this.document);
  2031. try { newRange.setStart(rangeBackup.startContainer, rangeBackup.startOffset); } catch(e1) {}
  2032. try { newRange.setEnd(rangeBackup.endContainer, rangeBackup.endOffset); } catch(e2) {}
  2033. try { this.SetSelection(newRange); } catch(e3) {}
  2034. },
  2035. /**
  2036. * Insert html at the caret position and move the cursor after the inserted html
  2037. *
  2038. * @param {String} html HTML string to insert
  2039. */
  2040. InsertHTML: function(html)
  2041. {
  2042. var
  2043. range = rangy.createRange(this.document),
  2044. node = range.createContextualFragment(html),
  2045. lastChild = node.lastChild;
  2046. this.InsertNode(node);
  2047. if (lastChild)
  2048. {
  2049. this.SetAfter(lastChild);
  2050. }
  2051. },
  2052. /**
  2053. * Insert a node at the caret position and move the cursor behind it
  2054. *
  2055. * @param {Object} node HTML string to insert
  2056. */
  2057. InsertNode: function(node)
  2058. {
  2059. var range = this.GetRange();
  2060. if (range)
  2061. {
  2062. range.insertNode(node);
  2063. }
  2064. },
  2065. RemoveNode: function(node)
  2066. {
  2067. this.editor.On('OnHtmlContentChangedByControl');
  2068. var
  2069. parent = node.parentNode,
  2070. cursorNode = node.nextSibling;
  2071. BX.remove(node);
  2072. this.editor.util.Refresh(parent);
  2073. if (cursorNode)
  2074. {
  2075. this.editor.selection.SetBefore(cursorNode);
  2076. this.editor.Focus();
  2077. }
  2078. this.editor.synchro.StartSync(100);
  2079. },
  2080. /**
  2081. * Wraps current selection with the given node
  2082. *
  2083. * @param {Object} node The node to surround the selected elements with
  2084. * @param {Object} range Current range
  2085. */
  2086. Surround: function(node, range)
  2087. {
  2088. range = range || this.GetRange();
  2089. if (range)
  2090. {
  2091. try
  2092. {
  2093. // This only works when the range boundaries are not overlapping other elements
  2094. range.surroundContents(node);
  2095. this.SelectNode(node);
  2096. }
  2097. catch(e)
  2098. {
  2099. node.appendChild(range.extractContents());
  2100. range.insertNode(node);
  2101. }
  2102. }
  2103. },
  2104. /**
  2105. * Scroll the current caret position into the view
  2106. * TODO: Dirty hack ...might be a smarter way of doing this
  2107. */
  2108. // ScrollIntoView: function()
  2109. // {
  2110. // var doc = this.doc,
  2111. // hasScrollBars = doc.documentElement.scrollHeight > doc.documentElement.offsetHeight,
  2112. // tempElement = doc._wysihtml5ScrollIntoViewElement = doc._wysihtml5ScrollIntoViewElement || (function() {
  2113. // var element = doc.createElement("span");
  2114. // // The element needs content in order to be able to calculate it's position properly
  2115. // element.innerHTML = wysihtml5.INVISIBLE_SPACE;
  2116. // return element;
  2117. // })(),
  2118. // offsetTop;
  2119. //
  2120. // if (hasScrollBars) {
  2121. // this.insertNode(tempElement);
  2122. // offsetTop = _getCumulativeOffsetTop(tempElement);
  2123. // tempElement.parentNode.removeChild(tempElement);
  2124. // if (offsetTop > doc.body.scrollTop) {
  2125. // doc.body.scrollTop = offsetTop;
  2126. // }
  2127. // }
  2128. // },
  2129. //
  2130. ScrollIntoView: function()
  2131. {
  2132. var
  2133. node,
  2134. _this = this,
  2135. doc = this.document,
  2136. bScrollBars = doc.documentElement.scrollHeight > doc.documentElement.offsetHeight;
  2137. if (bScrollBars)
  2138. {
  2139. var
  2140. tempNode = doc.__scrollIntoViewElement = doc.__scrollIntoViewElement || (function()
  2141. {
  2142. return BX.create("SPAN", {html: _this.editor.INVISIBLE_SPACE}, doc);
  2143. })(),
  2144. top = 0;
  2145. this.InsertNode(tempNode);
  2146. if (tempNode.parentNode)
  2147. {
  2148. node = tempNode;
  2149. do
  2150. {
  2151. top += node.offsetTop || 0;
  2152. node = node.offsetParent;
  2153. } while (node);
  2154. }
  2155. tempNode.parentNode.removeChild(tempNode);
  2156. // doc.documentElement.scrollTop or doc.body.scrollTop ?
  2157. if (top > doc.documentElement.scrollTop)
  2158. {
  2159. doc.documentElement.scrollTop = top;
  2160. }
  2161. }
  2162. },
  2163. /**
  2164. * Select line where the caret is in
  2165. */
  2166. SelectLine: function()
  2167. {
  2168. // See https://developer.mozilla.org/en/DOM/Selection/modify
  2169. var bSelectionModify = "getSelection" in window && "modify" in window.getSelection();
  2170. if (bSelectionModify)
  2171. {
  2172. var
  2173. win = this.document.defaultView,
  2174. selection = win.getSelection();
  2175. selection.modify("move", "left", "lineboundary");
  2176. selection.modify("extend", "right", "lineboundary");
  2177. }
  2178. else if (this.document.selection) // IE
  2179. {
  2180. var
  2181. range = this.document.selection.createRange(),
  2182. rangeTop = range.boundingTop,
  2183. rangeHeight = range.boundingHeight,
  2184. scrollWidth = this.document.body.scrollWidth,
  2185. rangeBottom,
  2186. rangeEnd,
  2187. measureNode,
  2188. i,
  2189. j;
  2190. if (!range.moveToPoint)
  2191. return;
  2192. if (rangeTop === 0)
  2193. {
  2194. // Don't know why, but when the selection ends at the end of a line
  2195. // range.boundingTop is 0
  2196. measureNode = this.document.createElement("span");
  2197. this.insertNode(measureNode);
  2198. rangeTop = measureNode.offsetTop;
  2199. measureNode.parentNode.removeChild(measureNode);
  2200. }
  2201. rangeTop += 1;
  2202. for (i =- 10; i < scrollWidth; i += 2)
  2203. {
  2204. try {
  2205. range.moveToPoint(i, rangeTop);
  2206. break;
  2207. } catch(e1) {}
  2208. }
  2209. // Investigate the following in order to handle multi line selections
  2210. // rangeBottom = rangeTop + (rangeHeight ? (rangeHeight - 1) : 0);
  2211. rangeBottom = rangeTop;
  2212. rangeEnd = this.document.selection.createRange();
  2213. for (j = scrollWidth; j >= 0; j--)
  2214. {
  2215. try
  2216. {
  2217. rangeEnd.moveToPoint(j, rangeBottom);
  2218. break;
  2219. } catch(e2) {}
  2220. }
  2221. range.setEndPoint("EndToEnd", rangeEnd);
  2222. range.select();
  2223. }
  2224. },
  2225. GetText: function()
  2226. {
  2227. var selection = this.GetSelection();
  2228. return selection ? selection.toString() : "";
  2229. },
  2230. GetNodes: function(nodeType, filter)
  2231. {
  2232. var range = this.GetRange();
  2233. if (range)
  2234. return range.getNodes([nodeType], filter);
  2235. else
  2236. return [];
  2237. },
  2238. GetRange: function()
  2239. {
  2240. var selection = this.GetSelection();
  2241. return selection && selection.rangeCount && selection.getRangeAt(0);
  2242. },
  2243. GetSelection: function()
  2244. {
  2245. return rangy.getSelection(this.document.defaultView || this.document.parentWindow);
  2246. },
  2247. SetSelection: function(range)
  2248. {
  2249. var
  2250. win = this.document.defaultView || this.document.parentWindow,
  2251. selection = rangy.getSelection(win);
  2252. return selection.setSingleRange(range);
  2253. },
  2254. GetStructuralTags: function()
  2255. {
  2256. if (!this.structuralTags)
  2257. {
  2258. var tblRe = /^TABLE/i;
  2259. this.structuralTags = {
  2260. 'LI': /^UL|OL|MENU/i,
  2261. 'DT': /^DL/i,
  2262. 'DD': /^DL/i,
  2263. // table
  2264. 'TD': tblRe,
  2265. 'TR': tblRe,
  2266. 'TH': tblRe,
  2267. 'TBODY': tblRe,
  2268. 'TFOOT': tblRe,
  2269. 'THEAD': tblRe,
  2270. 'CAPTION': tblRe,
  2271. 'COL': tblRe,
  2272. 'COLGROUP': tblRe
  2273. };
  2274. this.structuralTagsMatchRe = /^LI|DT|DD|TD|TR|TH|TBODY|CAPTION|COL|COLGROUP|TFOOT|THEAD/i;
  2275. }
  2276. return this.structuralTags;
  2277. },
  2278. SetCursorAfterNode: function(e)
  2279. {
  2280. var
  2281. _this = this,
  2282. parent,
  2283. range = this.GetRange();
  2284. this.GetStructuralTags();
  2285. function checkLastState(range)
  2286. {
  2287. return _this.lastCheckedRange && _this.lastCheckedRange.endOffset == range.endOffset && _this.lastCheckedRange.endContainer == range.endContainer;
  2288. }
  2289. function getNonTextLastChild(n)
  2290. {
  2291. var res = n.lastChild;
  2292. while (res.nodeType != 1 && res.previousSibling)
  2293. res = res.previousSibling;
  2294. return res.nodeType == 1 ? res : false;
  2295. }
  2296. function moveCursorAfterNode(node)
  2297. {
  2298. var possibleParentRe, parNode, isLastNode, invis_text;
  2299. // Check if it's child node which have special parent
  2300. // We can't add text node and put carret <td> and <tr> or between <li>...
  2301. // So we trying handle the only case when carret in the end of the last child of last child of our structural tags (UL, OL, MENU, DIR, TABLE, DL)
  2302. if (node.nodeName.match(_this.structuralTagsMatchRe))
  2303. {
  2304. isLastNode = getNonTextLastChild(node.parentNode) === node;
  2305. if (!isLastNode)
  2306. {
  2307. return;
  2308. }
  2309. possibleParentRe = _this.structuralTags[node.nodeName];
  2310. if (possibleParentRe)
  2311. {
  2312. parNode = BX.findParent(node, function(n)
  2313. {
  2314. if (n.nodeName.match(possibleParentRe))
  2315. {
  2316. return true;
  2317. }
  2318. isLastNode = isLastNode && getNonTextLastChild(n.parentNode) === n;
  2319. return false;
  2320. }, node.ownerDocument.BODY);
  2321. if (parNode && isLastNode)
  2322. {
  2323. node = parNode; // Put carret after parrent tag
  2324. }
  2325. else
  2326. {
  2327. return;
  2328. }
  2329. }
  2330. }
  2331. _this.SetInvisibleTextAfterNode(node);
  2332. }
  2333. if (range.collapsed && _this.CheckLastRange(range))
  2334. {
  2335. var
  2336. node = range.endContainer,
  2337. isEmpty = _this.editor.util.IsEmptyNode(node);
  2338. if (node.nodeType == 3 && node.length == range.endOffset && !isEmpty)
  2339. {
  2340. parent = node.parentNode;
  2341. if (parent && node === parent.lastChild && parent.nodeName != "BODY")
  2342. {
  2343. moveCursorAfterNode(parent);
  2344. }
  2345. }
  2346. else if (node.nodeType == 1 && node.nodeName != "BODY" && !isEmpty)
  2347. {
  2348. moveCursorAfterNode(node);
  2349. BX.PreventDefault(e);
  2350. }
  2351. }
  2352. },
  2353. SetCursorBeforeNode: function(e)
  2354. {
  2355. var
  2356. _this = this,
  2357. parent,
  2358. range = this.GetRange();
  2359. this.GetStructuralTags();
  2360. function getNonTextFirstChild(n)
  2361. {
  2362. var res = n.firstChild;
  2363. while (res.nodeType != 1 && res.nextSibling)
  2364. res = res.nextSibling;
  2365. return res.nodeType == 1 ? res : false;
  2366. }
  2367. function moveCursorBeforeNode(node)
  2368. {
  2369. var possibleParentRe, parNode, isFirstNode, invis_text;
  2370. // Check if it's child node which have special parent
  2371. // We can't add text node and put carret <td> and <tr> or between <li>...
  2372. // So we trying handle the only case when carret before beginning of the first child of our structural tags (UL, OL, MENU, DIR, TABLE, DL)
  2373. if (node.nodeName.match(_this.structuralTagsMatchRe))
  2374. {
  2375. isFirstNode = getNonTextFirstChild(node.parentNode) === node;
  2376. if (!isFirstNode)
  2377. {
  2378. return;
  2379. }
  2380. possibleParentRe = _this.structuralTags[node.nodeName];
  2381. if (possibleParentRe)
  2382. {
  2383. parNode = BX.findParent(node, function(n)
  2384. {
  2385. if (n.nodeName.match(possibleParentRe))
  2386. {
  2387. return true;
  2388. }
  2389. isFirstNode = isFirstNode && getNonTextFirstChild(n.parentNode) === n;
  2390. return false;
  2391. }, node.ownerDocument.BODY);
  2392. if (parNode && isFirstNode)
  2393. {
  2394. node = parNode; // Put carret before parrent tag
  2395. }
  2396. else
  2397. {
  2398. return;
  2399. }
  2400. }
  2401. }
  2402. _this.SetInvisibleTextBeforeNode(node);
  2403. }
  2404. if (range.collapsed && _this.CheckLastRange(range))
  2405. {
  2406. var
  2407. node = range.startContainer,
  2408. isEmpty = _this.editor.util.IsEmptyNode(node);
  2409. // Now cursor inside node on the first position and user want to move it before node
  2410. if (node.nodeType == 3 && range.startOffset == 0 && !isEmpty)
  2411. {
  2412. parent = node.parentNode;
  2413. if (parent && node === parent.firstChild && parent.nodeName != "BODY")
  2414. {
  2415. moveCursorBeforeNode(parent);
  2416. }
  2417. }
  2418. else if (node.nodeType == 1 && node.nodeName != "BODY" && !isEmpty)
  2419. {
  2420. moveCursorBeforeNode(node);
  2421. BX.PreventDefault(e);
  2422. }
  2423. }
  2424. },
  2425. SaveRange: function()
  2426. {
  2427. var range = this.GetRange();
  2428. this.lastCheckedRange = {endOffset: range.endOffset, endContainer: range.endContainer};
  2429. },
  2430. CheckLastRange: function(range)
  2431. {
  2432. return this.lastCheckedRange && this.lastCheckedRange.endOffset == range.endOffset && this.lastCheckedRange.endContainer == range.endContainer;
  2433. },
  2434. SetInvisibleTextAfterNode: function(node)
  2435. {
  2436. var invis_text;
  2437. if (node.nextSibling && node.nextSibling.nodeType == 3 && this.editor.util.IsEmptyNode(node.nextSibling))
  2438. {
  2439. invis_text = node.nextSibling;
  2440. }
  2441. else
  2442. {
  2443. invis_text = this.editor.util.GetInvisibleTextNode();
  2444. }
  2445. this.editor.util.InsertAfter(invis_text, node);
  2446. this.SetAfter(invis_text);
  2447. this.editor.Focus();
  2448. },
  2449. SetInvisibleTextBeforeNode: function(node)
  2450. {
  2451. var invis_text;
  2452. if (node.previousSibling && node.previousSibling.nodeType == 3 && this.editor.util.IsEmptyNode(node.previousSibling))
  2453. {
  2454. invis_text = node.previousSibling;
  2455. }
  2456. else
  2457. {
  2458. invis_text = this.editor.util.GetInvisibleTextNode();
  2459. }
  2460. node.parentNode.insertBefore(invis_text, node);
  2461. this.SetBefore(invis_text);
  2462. this.editor.Focus();
  2463. },
  2464. GetCommonAncestorForRange: function(range)
  2465. {
  2466. return range.collapsed ?
  2467. range.startContainer :
  2468. rangy.dom.getCommonAncestor(range.startContainer, range.endContainer);
  2469. }
  2470. };
  2471. function NodeMerge(firstNode)
  2472. {
  2473. this.isElementMerge = (firstNode.nodeType == 1);
  2474. this.firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
  2475. this.textNodes = [this.firstTextNode];
  2476. }
  2477. NodeMerge.prototype = {
  2478. DoMerge: function()
  2479. {
  2480. var textBits = [], textNode, parent, text;
  2481. for (var i = 0, len = this.textNodes.length; i < len; ++i)
  2482. {
  2483. textNode = this.textNodes[i];
  2484. parent = textNode.parentNode;
  2485. textBits[i] = textNode.data;
  2486. if (i)
  2487. {
  2488. parent.removeChild(textNode);
  2489. if (!parent.hasChildNodes())
  2490. parent.parentNode.removeChild(parent);
  2491. }
  2492. }
  2493. this.firstTextNode.data = text = textBits.join("");
  2494. return text;
  2495. },
  2496. GetLength: function()
  2497. {
  2498. var i = this.textNodes.length, len = 0;
  2499. while (i--)
  2500. len += this.textNodes[i].length;
  2501. return len;
  2502. }
  2503. // ToString: function()
  2504. // {
  2505. // var textBits = [];
  2506. //
  2507. // for (var i = 0, len = this.textNodes.length; i < len; ++i)
  2508. // textBits[i] = "'" + this.textNodes[i].data + "'";
  2509. //
  2510. // return "[Merge(" + textBits.join(",") + ")]";
  2511. // }
  2512. };
  2513. function HTMLStyler(editor, tagNames, arStyle, cssClass, normalize)
  2514. {
  2515. this.editor = editor;
  2516. this.document = editor.iframeView.document;
  2517. this.tagNames = tagNames || [defaultTagName];
  2518. this.arStyle = arStyle || {};
  2519. this.cssClass = cssClass || "";
  2520. this.similarClassRegExp = null;
  2521. this.normalize = normalize;
  2522. this.applyToAnyTagName = false;
  2523. }
  2524. HTMLStyler.prototype =
  2525. {
  2526. GetStyledParent: function(node, bMatchCss)
  2527. {
  2528. bMatchCss = bMatchCss !== false;
  2529. var cssClassMatch, cssStyleMatch;
  2530. while (node)
  2531. {
  2532. if (node.nodeType == 1)
  2533. {
  2534. cssStyleMatch = this.CheckCssStyle(node, bMatchCss);
  2535. cssClassMatch = this.CheckCssClass(node);
  2536. if (BX.util.in_array(node.tagName.toLowerCase(), this.tagNames) && cssClassMatch && cssStyleMatch)
  2537. return node;
  2538. }
  2539. node = node.parentNode;
  2540. }
  2541. return false;
  2542. },
  2543. CheckCssStyle: function(node, bMatch)
  2544. {
  2545. return this.editor.util.CheckCss(node, this.arStyle, bMatch);
  2546. },
  2547. SimplifyNodesWithCss: function(node)
  2548. {
  2549. var i, parent = node.parentNode;
  2550. if (parent.childNodes.length == 1) // container over our node
  2551. {
  2552. if (node.nodeName == parent.nodeName)
  2553. {
  2554. for (i in this.arStyle)
  2555. {
  2556. if (this.arStyle.hasOwnProperty(i) && node.style[i])
  2557. {
  2558. parent.style[i] = node.style[i];
  2559. }
  2560. }
  2561. this.editor.util.ReplaceWithOwnChildren(node);
  2562. }
  2563. else
  2564. {
  2565. for (i in this.arStyle)
  2566. {
  2567. if (this.arStyle.hasOwnProperty(i) && parent.style[i] && node.style[i])
  2568. {
  2569. parent.style[i] = '';
  2570. }
  2571. }
  2572. }
  2573. }
  2574. },
  2575. CheckCssClass: function(node)
  2576. {
  2577. return !this.cssClass || (this.cssClass && BX.hasClass(node, this.cssClass));
  2578. },
  2579. // Normalizes nodes after applying a CSS class to a Range.
  2580. PostApply: function(textNodes, range)
  2581. {
  2582. var
  2583. i,
  2584. firstNode = textNodes[0],
  2585. lastNode = textNodes[textNodes.length - 1],
  2586. merges = [],
  2587. currentMerge,
  2588. rangeStartNode = firstNode,
  2589. rangeEndNode = lastNode,
  2590. rangeStartOffset = 0,
  2591. rangeEndOffset = lastNode.length,
  2592. textNode, precedingTextNode;
  2593. for (i = 0; i < textNodes.length; ++i)
  2594. {
  2595. textNode = textNodes[i];
  2596. precedingTextNode = this.GetAdjacentMergeableTextNode(textNode.parentNode, false);
  2597. if (precedingTextNode)
  2598. {
  2599. if (!currentMerge)
  2600. {
  2601. currentMerge = new NodeMerge(precedingTextNode);
  2602. merges.push(currentMerge);
  2603. }
  2604. currentMerge.textNodes.push(textNode);
  2605. if (textNode === firstNode)
  2606. {
  2607. rangeStartNode = currentMerge.firstTextNode;
  2608. rangeStartOffset = rangeStartNode.length;
  2609. }
  2610. if (textNode === lastNode)
  2611. {
  2612. rangeEndNode = currentMerge.firstTextNode;
  2613. rangeEndOffset = currentMerge.GetLength();
  2614. }
  2615. }
  2616. else
  2617. {
  2618. currentMerge = null;
  2619. }
  2620. }
  2621. // Test whether the first node after the range needs merging
  2622. var nextTextNode = this.GetAdjacentMergeableTextNode(lastNode.parentNode, true);
  2623. if (nextTextNode)
  2624. {
  2625. if (!currentMerge)
  2626. {
  2627. currentMerge = new NodeMerge(lastNode);
  2628. merges.push(currentMerge);
  2629. }
  2630. currentMerge.textNodes.push(nextTextNode);
  2631. }
  2632. // Do the merges
  2633. if (merges.length)
  2634. {
  2635. for (i = 0; i < merges.length; ++i)
  2636. merges[i].DoMerge();
  2637. // Set the range boundaries
  2638. range.setStart(rangeStartNode, rangeStartOffset);
  2639. range.setEnd(rangeEndNode, rangeEndOffset);
  2640. }
  2641. // Simplify elements
  2642. textNodes = range.getNodes([3]);
  2643. for (i = 0; i < textNodes.length; ++i)
  2644. {
  2645. textNode = textNodes[i];
  2646. this.SimplifyNodesWithCss(textNode.parentNode);
  2647. }
  2648. },
  2649. NormalizeNewNode: function(node, range)
  2650. {
  2651. var
  2652. parent = node.parentNode;
  2653. if (parent && parent.nodeName !== 'BODY')
  2654. {
  2655. var
  2656. childs = this.GetNonEmptyChilds(parent),
  2657. cssStyleMatch = this.CheckCssStyle(parent, false),
  2658. cssClassMatch = this.CheckCssClass(parent);
  2659. if (childs.length == 1 && parent.nodeName == node.nodeName && cssStyleMatch && cssClassMatch)
  2660. {
  2661. parent.parentNode.insertBefore(node, parent);
  2662. BX.remove(parent);
  2663. }
  2664. }
  2665. return range;
  2666. },
  2667. GetNonEmptyChilds: function(node)
  2668. {
  2669. var
  2670. i,
  2671. childs = node.childNodes,
  2672. res = [];
  2673. for (i = 0; i < childs.length; i++)
  2674. {
  2675. if (childs[i].nodeType == 1 ||
  2676. (childs[i].nodeType == 3
  2677. && childs[i].nodeValue != ""
  2678. && childs[i].nodeValue != this.editor.INVISIBLE_SPACE
  2679. && !childs[i].nodeValue.match(/^[\s\n\r\t]+$/ig)))
  2680. {
  2681. res.push(childs[i]);
  2682. }
  2683. }
  2684. return res;
  2685. },
  2686. GetAdjacentMergeableTextNode: function(node, forward)
  2687. {
  2688. var
  2689. isTextNode = (node.nodeType == 3),
  2690. el = isTextNode ? node.parentNode : node,
  2691. adjacentNode,
  2692. propName = forward ? "nextSibling" : "previousSibling";
  2693. if (isTextNode)
  2694. {
  2695. // Can merge if the node's previous/next sibling is a text node
  2696. adjacentNode = node[propName];
  2697. if (adjacentNode && adjacentNode.nodeType == 3)
  2698. {
  2699. return adjacentNode;
  2700. }
  2701. }
  2702. else
  2703. {
  2704. // Compare element with its sibling
  2705. adjacentNode = el[propName];
  2706. if (adjacentNode && this.AreElementsMergeable(node, adjacentNode))
  2707. {
  2708. return adjacentNode[forward ? "firstChild" : "lastChild"];
  2709. }
  2710. }
  2711. return null;
  2712. },
  2713. AreElementsMergeable: function(el1, el2)
  2714. {
  2715. return rangy.dom.arrayContains(this.tagNames, (el1.tagName || "").toLowerCase())
  2716. && rangy.dom.arrayContains(this.tagNames, (el2.tagName || "").toLowerCase())
  2717. && el1.className.replace(/\s+/g, " ") == el2.className.replace(/\s+/g, " ")
  2718. && this.CompareNodeAttributes(el1, el2);
  2719. },
  2720. CompareNodeAttributes: function(el1, el2)
  2721. {
  2722. if (el1.attributes.length != el2.attributes.length)
  2723. return false;
  2724. var i, len = el1.attributes.length, attr1, attr2, name;
  2725. for (i = 0; i < len; i++)
  2726. {
  2727. attr1 = el1.attributes[i];
  2728. name = attr1.name;
  2729. if (name != "class")
  2730. {
  2731. attr2 = el2.attributes.getNamedItem(name);
  2732. if (attr1.specified != attr2.specified)
  2733. return false;
  2734. if (attr1.specified && attr1.nodeValue !== attr2.nodeValue)
  2735. {
  2736. return false;
  2737. }
  2738. }
  2739. }
  2740. return true;
  2741. },
  2742. CreateContainer: function()
  2743. {
  2744. var el = this.document.createElement(this.tagNames[0]);
  2745. if (this.cssClass)
  2746. {
  2747. el.className = this.cssClass;
  2748. }
  2749. if (this.arStyle)
  2750. {
  2751. for (var i in this.arStyle)
  2752. {
  2753. if (this.arStyle.hasOwnProperty(i))
  2754. {
  2755. el.style[i] = this.arStyle[i];
  2756. }
  2757. }
  2758. }
  2759. return el;
  2760. },
  2761. ApplyToTextNode: function(textNode)
  2762. {
  2763. var parent = textNode.parentNode, i;
  2764. if (parent.childNodes.length == 1 && BX.util.in_array(parent.tagName.toLowerCase(), this.tagNames))
  2765. {
  2766. if (this.cssClass)
  2767. {
  2768. BX.addClass(parent, this.cssClass);
  2769. }
  2770. if (this.arStyle)
  2771. {
  2772. for (i in this.arStyle)
  2773. {
  2774. if (this.arStyle.hasOwnProperty(i))
  2775. {
  2776. parent.style[i] = this.arStyle[i];
  2777. }
  2778. }
  2779. }
  2780. }
  2781. else
  2782. {
  2783. if (parent.childNodes.length == 1) // container over the text node
  2784. {
  2785. if (this.cssClass && BX.hasClass(parent, this.cssClass))
  2786. {
  2787. BX.removeClass(parent, this.cssClass);
  2788. }
  2789. if (this.arStyle)
  2790. {
  2791. for (i in this.arStyle)
  2792. {
  2793. if (this.arStyle.hasOwnProperty(i) && parent.style[i])
  2794. {
  2795. parent.style[i] = '';
  2796. }
  2797. }
  2798. }
  2799. }
  2800. var el = this.CreateContainer();
  2801. textNode.parentNode.insertBefore(el, textNode);
  2802. el.appendChild(textNode);
  2803. }
  2804. },
  2805. IsRemovable: function(el)
  2806. {
  2807. return rangy.dom.arrayContains(this.tagNames, el.tagName.toLowerCase()) && BX.util.trim(el.className) == this.cssClass;
  2808. },
  2809. IsAvailableTextNodeParent: function(node)
  2810. {
  2811. return node && node.nodeName &&
  2812. node.nodeName !== 'OL' && node.nodeName !== 'UL' && node.nodeName !== 'MENU' && // LLIST
  2813. node.nodeName !== 'TBODY' && node.nodeName !== 'TFOOT' && node.nodeName !== 'THEAD' && node.nodeName !== 'TABLE' && // TABLE
  2814. node.nodeName !== 'DL';
  2815. },
  2816. UndoToTextNode: function(textNode, range, styledParent)
  2817. {
  2818. if (!range.containsNode(styledParent))
  2819. {
  2820. // Split out the portion of the parent from which we can remove the CSS class
  2821. var parentRange = range.cloneRange();
  2822. parentRange.selectNode(styledParent);
  2823. if (parentRange.isPointInRange(range.endContainer, range.endOffset) && this.editor.util.IsSplitPoint(range.endContainer, range.endOffset) && range.endContainer.nodeName !== 'BODY')
  2824. {
  2825. this.editor.util.SplitNodeAt(styledParent, range.endContainer, range.endOffset);
  2826. range.setEndAfter(styledParent);
  2827. }
  2828. if (parentRange.isPointInRange(range.startContainer, range.startOffset) && this.editor.util.IsSplitPoint(range.startContainer, range.startOffset) && range.startContainer.nodeName !== 'BODY')
  2829. {
  2830. styledParent = this.editor.util.SplitNodeAt(styledParent, range.startContainer, range.startOffset);
  2831. }
  2832. }
  2833. if (styledParent && styledParent.nodeName != 'BODY' && this.IsRemovable(styledParent))
  2834. {
  2835. if (this.arStyle)
  2836. {
  2837. for (var i in this.arStyle)
  2838. {
  2839. if (this.arStyle.hasOwnProperty(i) && styledParent.style[i])
  2840. {
  2841. styledParent.style[i] = '';
  2842. }
  2843. }
  2844. }
  2845. if (!styledParent.style.cssText || BX.util.trim(styledParent.style.cssText) === '')
  2846. {
  2847. this.editor.util.ReplaceWithOwnChildren(styledParent);
  2848. }
  2849. else if (this.tagNames.length > 1 || this.tagNames[0] !== 'span')
  2850. {
  2851. this.editor.util.RenameNode(styledParent, "span");
  2852. }
  2853. }
  2854. },
  2855. ApplyToRange: function(range)
  2856. {
  2857. var textNodes = range.getNodes([3]);
  2858. if (!textNodes.length)
  2859. {
  2860. try {
  2861. var node = this.CreateContainer();
  2862. range.surroundContents(node);
  2863. range = this.NormalizeNewNode(node, range);
  2864. this.SelectNode(range, node);
  2865. return range;
  2866. } catch(e) {}
  2867. }
  2868. range.splitBoundaries();
  2869. textNodes = range.getNodes([3]);
  2870. if (textNodes.length)
  2871. {
  2872. var textNode;
  2873. for (var i = 0, len = textNodes.length; i < len; ++i)
  2874. {
  2875. textNode = textNodes[i];
  2876. if (!this.GetStyledParent(textNode) && this.IsAvailableTextNodeParent(textNode.parentNode))
  2877. {
  2878. this.ApplyToTextNode(textNode);
  2879. }
  2880. }
  2881. range.setStart(textNodes[0], 0);
  2882. textNode = textNodes[textNodes.length - 1];
  2883. range.setEnd(textNode, textNode.length);
  2884. if (this.normalize)
  2885. {
  2886. this.PostApply(textNodes, range);
  2887. }
  2888. }
  2889. return range;
  2890. },
  2891. UndoToRange: function(range, bMatchCss)
  2892. {
  2893. var
  2894. textNodes = range.getNodes([3]),
  2895. textNode,
  2896. styledParent;
  2897. bMatchCss = bMatchCss !== false;
  2898. if (textNodes.length)
  2899. {
  2900. range.splitBoundaries();
  2901. textNodes = range.getNodes([3]);
  2902. }
  2903. else
  2904. {
  2905. var node = this.editor.util.GetInvisibleTextNode();
  2906. range.insertNode(node);
  2907. range.selectNode(node);
  2908. textNodes = [node];
  2909. }
  2910. var i, len, sorted = [];
  2911. for (i = 0, len = textNodes.length; i < len; i++)
  2912. {
  2913. sorted.push({node: textNodes[i], nesting: this.GetNodeNesting(textNodes[i])});
  2914. }
  2915. sorted = sorted.sort(function(a, b){return b.nesting - a.nesting});
  2916. for (i = 0, len = sorted.length; i < len; i++)
  2917. {
  2918. textNode = sorted[i].node;
  2919. styledParent = this.GetStyledParent(textNode, bMatchCss);
  2920. if (styledParent)
  2921. {
  2922. this.UndoToTextNode(textNode, range, styledParent);
  2923. range = this.editor.selection.GetRange();
  2924. }
  2925. }
  2926. if (len == 1)
  2927. {
  2928. this.SelectNode(range, textNodes[0]);
  2929. }
  2930. else
  2931. {
  2932. range.setStart(textNodes[0], 0);
  2933. range.setEnd(textNodes[textNodes.length - 1], textNodes[textNodes.length - 1].length);
  2934. this.editor.selection.SetSelection(range);
  2935. if (this.normalize)
  2936. {
  2937. this.PostApply(textNodes, range);
  2938. }
  2939. }
  2940. return range;
  2941. },
  2942. // Node dom offset
  2943. GetNodeNesting: function(node)
  2944. {
  2945. return this.editor.util.GetNodeDomOffset(node);
  2946. },
  2947. SelectNode: function(range, node)
  2948. {
  2949. var
  2950. isElement = node.nodeType === 1,
  2951. canHaveHTML = "canHaveHTML" in node ? node.canHaveHTML : true,
  2952. content = isElement ? node.innerHTML : node.data,
  2953. isEmpty = (content === "" || content === this.editor.INVISIBLE_SPACE);
  2954. if (isEmpty && isElement && canHaveHTML)
  2955. {
  2956. // Make sure that caret is visible in node by inserting a zero width no breaking space
  2957. try {node.innerHTML = this.editor.INVISIBLE_SPACE;} catch(e) {}
  2958. }
  2959. range.selectNodeContents(node);
  2960. if (isEmpty && isElement)
  2961. {
  2962. range.collapse(false);
  2963. }
  2964. else if (isEmpty)
  2965. {
  2966. range.setStartAfter(node);
  2967. range.setEndAfter(node);
  2968. }
  2969. },
  2970. GetTextSelectedByRange: function(textNode, range)
  2971. {
  2972. var textRange = range.cloneRange();
  2973. textRange.selectNodeContents(textNode);
  2974. var intersectionRange = textRange.intersection(range);
  2975. var text = intersectionRange ? intersectionRange.toString() : "";
  2976. textRange.detach();
  2977. return text;
  2978. },
  2979. IsAppliedToRange: function(range, bMatchCss)
  2980. {
  2981. var
  2982. parents = [],
  2983. parent,
  2984. textNodes = range.getNodes([3]);
  2985. bMatchCss = bMatchCss !== false;
  2986. if (!textNodes.length)
  2987. {
  2988. parent = this.GetStyledParent(range.startContainer, bMatchCss);
  2989. return parent ? [parent] : false;
  2990. }
  2991. var i, selectedText;
  2992. for (i = 0; i < textNodes.length; ++i)
  2993. {
  2994. selectedText = this.GetTextSelectedByRange(textNodes[i], range);
  2995. parent = this.GetStyledParent(textNodes[i], bMatchCss);
  2996. if (selectedText != "" && !parent)
  2997. {
  2998. return false;
  2999. }
  3000. else
  3001. {
  3002. parents.push(parent);
  3003. }
  3004. }
  3005. return parents;
  3006. },
  3007. ToggleRange: function(range)
  3008. {
  3009. return this.IsAppliedToRange(range) ? this.UndoToRange(range) : this.ApplyToRange(range);
  3010. }
  3011. };
  3012. function BXEditorUndoManager(editor)
  3013. {
  3014. this.editor = editor;
  3015. this.history = [this.editor.iframeView.GetValue()];
  3016. this.position = 1;
  3017. this.document = editor.sandbox.GetDocument();
  3018. this.historyLength = 30;
  3019. BX.addCustomEvent(this.editor, 'OnIframeReInit', BX.proxy(function(){this.document = this.editor.sandbox.GetDocument();}, this));
  3020. this.Init();
  3021. }
  3022. BXEditorUndoManager.prototype = {
  3023. Init: function()
  3024. {
  3025. var _this = this;
  3026. BX.addCustomEvent(this.editor, "OnHtmlContentChangedByControl", BX.delegate(this.Transact, this));
  3027. BX.addCustomEvent(this.editor, "OnIframeNewWord", BX.delegate(this.Transact, this));
  3028. BX.addCustomEvent(this.editor, "OnIframeKeyup", BX.delegate(this.Transact, this));
  3029. BX.addCustomEvent(this.editor, "OnBeforeCommandExec", function(isContentAction)
  3030. {
  3031. if (isContentAction)
  3032. {
  3033. _this.Transact();
  3034. }
  3035. });
  3036. // CTRL+Z and CTRL+Y and handle DEL & BACKSPACE
  3037. BX.addCustomEvent(this.editor, "OnIframeKeydown", BX.proxy(this.Keydown, this));
  3038. },
  3039. Keydown: function(e, keyCode, command, selectedNode)
  3040. {
  3041. if (e.ctrlKey || e.metaKey)
  3042. {
  3043. var
  3044. isUndo = keyCode === this.editor.KEY_CODES['z'] && !e.shiftKey,
  3045. isRedo = (keyCode === this.editor.KEY_CODES['z'] && e.shiftKey) || (keyCode === this.editor.KEY_CODES['y']);
  3046. if (isUndo)
  3047. {
  3048. this.Undo();
  3049. return BX.PreventDefault(e);
  3050. }
  3051. else if (isRedo)
  3052. {
  3053. this.Redo();
  3054. return BX.PreventDefault(e);
  3055. }
  3056. }
  3057. if (keyCode !== this.lastKey)
  3058. {
  3059. if (keyCode === this.editor.KEY_CODES['backspace'] || keyCode === this.editor.KEY_CODES['delete'])
  3060. {
  3061. this.Transact();
  3062. }
  3063. this.lastKey = keyCode;
  3064. }
  3065. },
  3066. Transact: function()
  3067. {
  3068. var
  3069. previousHtml = this.history[this.position - 1],
  3070. currentHtml = this.editor.iframeView.GetValue();
  3071. if (currentHtml !== previousHtml)
  3072. {
  3073. var length = this.history.length = this.position;
  3074. if (length > this.historyLength)
  3075. {
  3076. this.history.shift();
  3077. this.position--;
  3078. }
  3079. this.position++;
  3080. this.history.push(currentHtml);
  3081. this.CheckControls();
  3082. }
  3083. },
  3084. Undo: function()
  3085. {
  3086. if (this.position > 1)
  3087. {
  3088. this.Transact();
  3089. this.position--;
  3090. this.Set(this.history[this.position - 1]);
  3091. this.editor.On("OnUndo");
  3092. this.CheckControls();
  3093. }
  3094. },
  3095. Redo: function()
  3096. {
  3097. if (this.position < this.history.length)
  3098. {
  3099. this.position++;
  3100. this.Set(this.history[this.position - 1]);
  3101. this.editor.On("OnRedo");
  3102. this.CheckControls();
  3103. }
  3104. },
  3105. Set: function(html)
  3106. {
  3107. this.editor.iframeView.SetValue(html);
  3108. this.editor.Focus(true);
  3109. },
  3110. CheckControls: function()
  3111. {
  3112. this.editor.On("OnEnableUndo", [this.position > 1]);
  3113. this.editor.On("OnEnableRedo", [this.position < this.history.length]);
  3114. }
  3115. }
  3116. function BXStyles(editor)
  3117. {
  3118. this.editor = editor;
  3119. this.arStyles = {};
  3120. this.sStyles = '';
  3121. }
  3122. BXStyles.prototype = {
  3123. CreateIframe: function(styles)
  3124. {
  3125. this.cssIframe = document.body.appendChild(BX.create("IFRAME", {props: {className: "bx-editor-css-iframe"}}));
  3126. this.iframeDocument = this.cssIframe.contentDocument || this.cssIframe.contentWindow.document;
  3127. this.iframeDocument.open("text/html", "replace");
  3128. this.iframeDocument.write('<!DOCTYPE html><html><head><style type="text/css" data-bx-template-style="Y">' + styles + '</style></head><body></body></html>');
  3129. this.iframeDocument.close();
  3130. },
  3131. GetCSS: function(templateId, styles, templatePath, filter)
  3132. {
  3133. if (!this.arStyles[templateId])
  3134. {
  3135. if (!this.cssIframe)
  3136. {
  3137. this.cssIframe = this.CreateIframe(styles);
  3138. }
  3139. else
  3140. {
  3141. var
  3142. i,
  3143. doc = this.iframeDocument,
  3144. head = doc.head || doc.getElementsByTagName('HEAD')[0],
  3145. styleNodes = head.getElementsByTagName('STYLE');
  3146. // Clean old template styles
  3147. for (i = 0; i < styleNodes.length; i++)
  3148. {
  3149. if (styleNodes[i].getAttribute('data-bx-template-style') == 'Y')
  3150. BX.cleanNode(styleNodes[i], true);
  3151. }
  3152. // Add new node in the iframe head
  3153. if (styles)
  3154. {
  3155. head.appendChild(BX.create('STYLE', {props: {type: 'text/css'}, text: styles}, doc)).setAttribute('data-bx-template-style', 'Y');
  3156. }
  3157. }
  3158. this.arStyles[templateId] = this.ParseCss();
  3159. }
  3160. var res = this.arStyles[templateId];
  3161. if (filter)
  3162. {
  3163. var filteredRes = [], tag;
  3164. if (typeof filter != 'object' )
  3165. {
  3166. filter = [filter];
  3167. }
  3168. filter.push('DEFAULT');
  3169. for (i = 0; i < filter.length; i++)
  3170. {
  3171. tag = filter[i];
  3172. if (res[tag] && typeof res[tag] == 'object')
  3173. {
  3174. filteredRes = filteredRes.concat(res[tag]);
  3175. }
  3176. }
  3177. res = filteredRes;
  3178. }
  3179. return res;
  3180. },
  3181. ParseCss: function()
  3182. {
  3183. var
  3184. doc = this.iframeDocument,
  3185. arAllSt = [],
  3186. result = {},
  3187. rules,
  3188. cssTag, arTags, i, j, k,
  3189. t1, t2, l1, l2, l3;
  3190. if(!doc.styleSheets)
  3191. {
  3192. return result;
  3193. }
  3194. var x1 = doc.styleSheets;
  3195. for(i = 0, l1 = x1.length; i < l1; i++)
  3196. {
  3197. rules = (x1[i].rules ? x1[i].rules : x1[i].cssRules);
  3198. for(j = 0, l2 = rules.length; j < l2; j++)
  3199. {
  3200. if (rules[j].type != rules[j].STYLE_RULE)
  3201. {
  3202. continue;
  3203. }
  3204. cssTag = rules[j].selectorText;
  3205. arTags = cssTag.split(",");
  3206. for(k = 0, l3 = arTags.length; k < l3; k++)
  3207. {
  3208. t1 = arTags[k].split(" ");
  3209. t1 = t1[t1.length - 1].trim();
  3210. if(t1.substr(0, 1) == '.')
  3211. {
  3212. t1 = t1.substr(1);
  3213. t2 = 'DEFAULT';
  3214. }
  3215. else
  3216. {
  3217. t2 = t1.split(".");
  3218. t1 = (t2.length > 1) ? t2[1] : '';
  3219. t2 = t2[0].toUpperCase();
  3220. }
  3221. if(!arAllSt[t1])
  3222. {
  3223. arAllSt[t1] = true;
  3224. if(!result[t2])
  3225. {
  3226. result[t2] = [];
  3227. }
  3228. result[t2].push({className: t1, original: arTags[k], cssText: rules[j].style.cssText});
  3229. }
  3230. }
  3231. }
  3232. }
  3233. return result;
  3234. }
  3235. };
  3236. // Parse rules
  3237. /**
  3238. * Full HTML5 compatibility rule set
  3239. * These rules define which tags and css classes are supported and which tags should be specially treated.
  3240. *
  3241. * Examples based on this rule set:
  3242. *
  3243. * <a href="http://foobar.com">foo</a>
  3244. * ... becomes ...
  3245. * <a href="http://foobar.com" target="_blank" rel="nofollow">foo</a>
  3246. *
  3247. * <img align="left" src="http://foobar.com/image.png">
  3248. * ... becomes ...
  3249. * <img class="wysiwyg-float-left" src="http://foobar.com/image.png" alt="">
  3250. *
  3251. * <div>foo<script>alert(document.cookie)</script></div>
  3252. * ... becomes ...
  3253. * <div>foo</div>
  3254. *
  3255. * <marquee>foo</marquee>
  3256. * ... becomes ...
  3257. * <span>foo</marquee>
  3258. *
  3259. * foo <br clear="both"> bar
  3260. * ... becomes ...
  3261. * foo <br class="wysiwyg-clear-both"> bar
  3262. *
  3263. * <div>hello <iframe src="http://google.com"></iframe></div>
  3264. * ... becomes ...
  3265. * <div>hello </div>
  3266. *
  3267. * <center>hello</center>
  3268. * ... becomes ...
  3269. * <div class="wysiwyg-text-align-center">hello</div>
  3270. */
  3271. __BXHtmlEditorParserRules = {
  3272. /**
  3273. * CSS Class white-list
  3274. * Following css classes won't be removed when parsed by the parser
  3275. */
  3276. classes: {},
  3277. "tags": {
  3278. "b": {clean_empty: true},
  3279. "strong": {clean_empty: true},
  3280. "i": {clean_empty: true},
  3281. "em": {clean_empty: true},
  3282. "u": {clean_empty: true},
  3283. "del": {clean_empty: true},
  3284. "s": {rename_tag: "del"},
  3285. "strike": {rename_tag: "del"},
  3286. // headers
  3287. "h1": {},
  3288. "h2": {},
  3289. "h3": {},
  3290. "h4": {},
  3291. "h5": {},
  3292. "h6": {},
  3293. // popular tags
  3294. "span": {clean_empty: true},
  3295. //"span": {replace_with_children: 1},
  3296. "p": {},
  3297. "br": {},
  3298. "div": {},
  3299. "hr": {},
  3300. "nobr": {},
  3301. "code": {},
  3302. // Lists
  3303. "menu": {rename_tag: "ul"}, // ??
  3304. "ol": {},
  3305. "ul": {},
  3306. "li": {},
  3307. "pre": {},
  3308. // Table
  3309. "table": {},
  3310. "tr": {
  3311. "add_class": {
  3312. "align": "align_text"
  3313. }
  3314. },
  3315. "td": {
  3316. "check_attributes": {
  3317. "rowspan": "numbers",
  3318. "colspan": "numbers"
  3319. },
  3320. "add_class": {
  3321. "align": "align_text"
  3322. }
  3323. },
  3324. "tbody": {
  3325. "add_class": {
  3326. "align": "align_text"
  3327. }
  3328. },
  3329. "tfoot": {
  3330. "add_class": {
  3331. "align": "align_text"
  3332. }
  3333. },
  3334. "thead": {
  3335. "add_class": {
  3336. "align": "align_text"
  3337. }
  3338. },
  3339. "th": {
  3340. "check_attributes": {
  3341. "rowspan": "numbers",
  3342. "colspan": "numbers"
  3343. },
  3344. "add_class": {
  3345. "align": "align_text"
  3346. }
  3347. },
  3348. "caption": {
  3349. "add_class": {
  3350. "align": "align_text"
  3351. }
  3352. },
  3353. // Definitions // <dl>, <dt>, <dd>
  3354. "dl": {rename_tag: ""},
  3355. "dd": {rename_tag: ""},
  3356. "dt": {rename_tag: ""},
  3357. "iframe": {},
  3358. "noindex": {},
  3359. // tags to remove
  3360. "title": {remove: 1},
  3361. "area": {remove: 1},
  3362. "command": {remove: 1},
  3363. "noframes": {remove: 1},
  3364. "bgsound": {remove: 1},
  3365. "basefont": {remove: 1},
  3366. "head": {remove: 1},
  3367. "object": {remove: 1},
  3368. "track": {remove: 1},
  3369. "wbr": {remove: 1},
  3370. "noscript": {remove: 1},
  3371. "svg": {remove: 1},
  3372. "input": {remove: 1},
  3373. "keygen": {remove: 1},
  3374. "meta": {remove: 1},
  3375. "isindex": {remove: 1},
  3376. "base": {remove: 1},
  3377. "video": {remove: 1},
  3378. "canvas": {remove: 1},
  3379. "applet": {remove: 1},
  3380. "spacer": {remove: 1},
  3381. "source": {remove: 1},
  3382. "frame": {remove: 1},
  3383. "style": {remove: 1},
  3384. "device": {remove: 1},
  3385. "embed": {remove: 1},
  3386. "noembed": {remove: 1},
  3387. "xml": {remove: 1},
  3388. "param": {remove: 1},
  3389. "nextid": {remove: 1},
  3390. "audio": {remove: 1},
  3391. "col": {remove: 1},
  3392. "link": {remove: 1},
  3393. "script": {remove: 1},
  3394. "colgroup": {remove: 1},
  3395. "comment": {remove: 1},
  3396. "frameset": {remove: 1},
  3397. // Tags to rename
  3398. // to DIV
  3399. "form": {rename_tag: "div"},
  3400. "details": {rename_tag: "div"},
  3401. "multicol": {rename_tag: "div"},
  3402. "figure": {rename_tag: "div"},
  3403. "figcaption": {rename_tag: "div"},
  3404. "footer": {rename_tag: "div"},
  3405. "address": {rename_tag: "div"},
  3406. "nav": {rename_tag: "div"},
  3407. "fieldset": {rename_tag: "div"},
  3408. "map": {rename_tag: "div"},
  3409. "aside": {rename_tag: "div"},
  3410. "section": {rename_tag: "div"},
  3411. "body": {rename_tag: "div"},
  3412. "html": {rename_tag: "div"},
  3413. "hgroup": {rename_tag: "div"},
  3414. "listing": {rename_tag: "div"},
  3415. "header": {rename_tag: "div"},
  3416. "article": {rename_tag: "div"},
  3417. // to SPAN
  3418. "rt": {rename_tag: "span"},
  3419. "acronym": {rename_tag: "span"},
  3420. "xmp": {rename_tag: "span"},
  3421. "small": {rename_tag: "span"},
  3422. "big": {rename_tag: "span"},
  3423. "time": {rename_tag: "span"},
  3424. "bdi": {rename_tag: "span"},
  3425. "progress": {rename_tag: "span"},
  3426. "dfn": {rename_tag: "span"},
  3427. "rb": {rename_tag: "span"},
  3428. "abbr": {rename_tag: "span"},
  3429. "sup": {rename_tag: "span"},
  3430. "option": {rename_tag: "span"},
  3431. "select": {rename_tag: "span"},
  3432. "button": {rename_tag: "span"},
  3433. "mark": {rename_tag: "span"},
  3434. "output": {rename_tag: "span"},
  3435. "marquee": {rename_tag: "span"},
  3436. "rp": {rename_tag: "span"},
  3437. "summary": {rename_tag: "span"},
  3438. "var": {rename_tag: "span"},
  3439. "tt": {rename_tag: "span"},
  3440. "blink": {rename_tag: "span"},
  3441. "plaintext": {rename_tag: "span"},
  3442. "legend": {rename_tag: "span"},
  3443. "label": {rename_tag: "span"},
  3444. "kbd": {rename_tag: "span"},
  3445. "meter": {rename_tag: "span"},
  3446. "textarea": {rename_tag: "span"},
  3447. "datalist": {rename_tag: "span"},
  3448. "samp": {rename_tag: "span"},
  3449. "bdo": {rename_tag: "span"},
  3450. "ruby": {rename_tag: "span"},
  3451. "ins": {rename_tag: "span"},
  3452. "sub": {rename_tag: "span"},
  3453. "optgroup": {rename_tag: "span"},
  3454. "dir": {rename_tag: "ul"},
  3455. "a": {
  3456. // "check_attributes": {
  3457. // "href": "url"
  3458. // },
  3459. //"set_attributes":
  3460. //{
  3461. //"rel": "nofollow",
  3462. //"target": "_blank"
  3463. //}
  3464. },
  3465. "img": {
  3466. "check_attributes": {
  3467. "width": "numbers",
  3468. "alt": "alt",
  3469. "src": "url",
  3470. "height": "numbers"
  3471. },
  3472. "add_class": {
  3473. "align": "align_img"
  3474. }
  3475. },
  3476. "q": {
  3477. "check_attributes": {
  3478. "cite": "url"
  3479. }
  3480. },
  3481. "blockquote": {
  3482. "check_attributes": {
  3483. "cite": "url"
  3484. }
  3485. },
  3486. "font": {
  3487. rename_tag: "span",
  3488. "add_class": {
  3489. "size": "size_font"
  3490. }
  3491. },
  3492. "center": {
  3493. rename_tag: "div",
  3494. add_css:
  3495. {
  3496. textAlign : 'center'
  3497. }
  3498. },
  3499. "cite": {}
  3500. }
  3501. };
  3502. })();