EnterKeyHandling.js.uncompressed.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. define("dijit/_editor/plugins/EnterKeyHandling", [
  2. "dojo/_base/declare", // declare
  3. "dojo/dom-construct", // domConstruct.destroy domConstruct.place
  4. "dojo/_base/event", // event.stop
  5. "dojo/keys", // keys.ENTER
  6. "dojo/_base/lang",
  7. "dojo/sniff", // has("ie") has("mozilla") has("webkit")
  8. "dojo/_base/window", // win.withGlobal
  9. "dojo/window", // winUtils.scrollIntoView
  10. "../_Plugin",
  11. "../RichText",
  12. "../range",
  13. "../../_base/focus"
  14. ], function(declare, domConstruct, event, keys, lang, has, win, winUtils, _Plugin, RichText, rangeapi, baseFocus){
  15. // module:
  16. // dijit/_editor/plugins/EnterKeyHandling
  17. return declare("dijit._editor.plugins.EnterKeyHandling", _Plugin, {
  18. // summary:
  19. // This plugin tries to make all browsers behave consistently with regard to
  20. // how ENTER behaves in the editor window. It traps the ENTER key and alters
  21. // the way DOM is constructed in certain cases to try to commonize the generated
  22. // DOM and behaviors across browsers.
  23. //
  24. // description:
  25. // This plugin has three modes:
  26. //
  27. // - blockNodeForEnter=BR
  28. // - blockNodeForEnter=DIV
  29. // - blockNodeForEnter=P
  30. //
  31. // In blockNodeForEnter=P, the ENTER key starts a new
  32. // paragraph, and shift-ENTER starts a new line in the current paragraph.
  33. // For example, the input:
  34. //
  35. // | first paragraph <shift-ENTER>
  36. // | second line of first paragraph <ENTER>
  37. // | second paragraph
  38. //
  39. // will generate:
  40. //
  41. // | <p>
  42. // | first paragraph
  43. // | <br/>
  44. // | second line of first paragraph
  45. // | </p>
  46. // | <p>
  47. // | second paragraph
  48. // | </p>
  49. //
  50. // In BR and DIV mode, the ENTER key conceptually goes to a new line in the
  51. // current paragraph, and users conceptually create a new paragraph by pressing ENTER twice.
  52. // For example, if the user enters text into an editor like this:
  53. //
  54. // | one <ENTER>
  55. // | two <ENTER>
  56. // | three <ENTER>
  57. // | <ENTER>
  58. // | four <ENTER>
  59. // | five <ENTER>
  60. // | six <ENTER>
  61. //
  62. // It will appear on the screen as two 'paragraphs' of three lines each. Markupwise, this generates:
  63. //
  64. // BR:
  65. // | one<br/>
  66. // | two<br/>
  67. // | three<br/>
  68. // | <br/>
  69. // | four<br/>
  70. // | five<br/>
  71. // | six<br/>
  72. //
  73. // DIV:
  74. // | <div>one</div>
  75. // | <div>two</div>
  76. // | <div>three</div>
  77. // | <div>&nbsp;</div>
  78. // | <div>four</div>
  79. // | <div>five</div>
  80. // | <div>six</div>
  81. // blockNodeForEnter: String
  82. // This property decides the behavior of Enter key. It can be either P,
  83. // DIV, BR, or empty (which means disable this feature). Anything else
  84. // will trigger errors. The default is 'BR'
  85. //
  86. // See class description for more details.
  87. blockNodeForEnter: 'BR',
  88. constructor: function(args){
  89. if(args){
  90. if("blockNodeForEnter" in args){
  91. args.blockNodeForEnter = args.blockNodeForEnter.toUpperCase();
  92. }
  93. lang.mixin(this,args);
  94. }
  95. },
  96. setEditor: function(editor){
  97. // Overrides _Plugin.setEditor().
  98. if(this.editor === editor){ return; }
  99. this.editor = editor;
  100. if(this.blockNodeForEnter == 'BR'){
  101. // While Moz has a mode tht mostly works, it's still a little different,
  102. // So, try to just have a common mode and be consistent. Which means
  103. // we need to enable customUndo, if not already enabled.
  104. this.editor.customUndo = true;
  105. editor.onLoadDeferred.then(lang.hitch(this,function(d){
  106. this.connect(editor.document, "onkeypress", function(e){
  107. if(e.charOrCode == keys.ENTER){
  108. // Just do it manually. The handleEnterKey has a shift mode that
  109. // Always acts like <br>, so just use it.
  110. var ne = lang.mixin({},e);
  111. ne.shiftKey = true;
  112. if(!this.handleEnterKey(ne)){
  113. event.stop(e);
  114. }
  115. }
  116. });
  117. if(has("ie") >= 9){
  118. this.connect(editor.document, "onpaste", function(e){
  119. setTimeout(dojo.hitch(this, function(){
  120. // Use the old range/selection code to kick IE 9 into updating
  121. // its range by moving it back, then forward, one 'character'.
  122. var r = this.editor.document.selection.createRange();
  123. r.move('character',-1);
  124. r.select();
  125. r.move('character',1);
  126. r.select();
  127. }),0);
  128. });
  129. }
  130. return d;
  131. }));
  132. }else if(this.blockNodeForEnter){
  133. // add enter key handler
  134. // FIXME: need to port to the new event code!!
  135. var h = lang.hitch(this,this.handleEnterKey);
  136. editor.addKeyHandler(13, 0, 0, h); //enter
  137. editor.addKeyHandler(13, 0, 1, h); //shift+enter
  138. this.connect(this.editor,'onKeyPressed','onKeyPressed');
  139. }
  140. },
  141. onKeyPressed: function(){
  142. // summary:
  143. // Handler for keypress events.
  144. // tags:
  145. // private
  146. if(this._checkListLater){
  147. if(win.withGlobal(this.editor.window, 'isCollapsed', baseFocus)){
  148. var liparent = this.editor._sCall('getAncestorElement', ['LI']);
  149. if(!liparent){
  150. // circulate the undo detection code by calling RichText::execCommand directly
  151. RichText.prototype.execCommand.call(this.editor, 'formatblock',this.blockNodeForEnter);
  152. // set the innerHTML of the new block node
  153. var block = this.editor._sCall('getAncestorElement', [this.blockNodeForEnter]);
  154. if(block){
  155. block.innerHTML=this.bogusHtmlContent;
  156. if(has("ie") <= 9){
  157. // move to the start by moving backwards one char
  158. var r = this.editor.document.selection.createRange();
  159. r.move('character',-1);
  160. r.select();
  161. }
  162. }else{
  163. console.error('onKeyPressed: Cannot find the new block node'); // FIXME
  164. }
  165. }else{
  166. if(has("mozilla")){
  167. if(liparent.parentNode.parentNode.nodeName == 'LI'){
  168. liparent=liparent.parentNode.parentNode;
  169. }
  170. }
  171. var fc=liparent.firstChild;
  172. if(fc && fc.nodeType == 1 && (fc.nodeName == 'UL' || fc.nodeName == 'OL')){
  173. liparent.insertBefore(fc.ownerDocument.createTextNode('\xA0'),fc);
  174. var newrange = rangeapi.create(this.editor.window);
  175. newrange.setStart(liparent.firstChild,0);
  176. var selection = rangeapi.getSelection(this.editor.window, true);
  177. selection.removeAllRanges();
  178. selection.addRange(newrange);
  179. }
  180. }
  181. }
  182. this._checkListLater = false;
  183. }
  184. if(this._pressedEnterInBlock){
  185. // the new created is the original current P, so we have previousSibling below
  186. if(this._pressedEnterInBlock.previousSibling){
  187. this.removeTrailingBr(this._pressedEnterInBlock.previousSibling);
  188. }
  189. delete this._pressedEnterInBlock;
  190. }
  191. },
  192. // bogusHtmlContent: [private] String
  193. // HTML to stick into a new empty block
  194. bogusHtmlContent: '&#160;', // &nbsp;
  195. // blockNodes: [private] Regex
  196. // Regex for testing if a given tag is a block level (display:block) tag
  197. blockNodes: /^(?:P|H1|H2|H3|H4|H5|H6|LI)$/,
  198. handleEnterKey: function(e){
  199. // summary:
  200. // Handler for enter key events when blockNodeForEnter is DIV or P.
  201. // description:
  202. // Manually handle enter key event to make the behavior consistent across
  203. // all supported browsers. See class description for details.
  204. // tags:
  205. // private
  206. var selection, range, newrange, startNode, endNode, brNode, doc=this.editor.document,br,rs,txt;
  207. if(e.shiftKey){ // shift+enter always generates <br>
  208. var parent = this.editor._sCall('getParentElement', []);
  209. var header = rangeapi.getAncestor(parent,this.blockNodes);
  210. if(header){
  211. if(header.tagName == 'LI'){
  212. return true; // let browser handle
  213. }
  214. selection = rangeapi.getSelection(this.editor.window);
  215. range = selection.getRangeAt(0);
  216. if(!range.collapsed){
  217. range.deleteContents();
  218. selection = rangeapi.getSelection(this.editor.window);
  219. range = selection.getRangeAt(0);
  220. }
  221. if(rangeapi.atBeginningOfContainer(header, range.startContainer, range.startOffset)){
  222. br=doc.createElement('br');
  223. newrange = rangeapi.create(this.editor.window);
  224. header.insertBefore(br,header.firstChild);
  225. newrange.setStartAfter(br);
  226. selection.removeAllRanges();
  227. selection.addRange(newrange);
  228. }else if(rangeapi.atEndOfContainer(header, range.startContainer, range.startOffset)){
  229. newrange = rangeapi.create(this.editor.window);
  230. br=doc.createElement('br');
  231. header.appendChild(br);
  232. header.appendChild(doc.createTextNode('\xA0'));
  233. newrange.setStart(header.lastChild,0);
  234. selection.removeAllRanges();
  235. selection.addRange(newrange);
  236. }else{
  237. rs = range.startContainer;
  238. if(rs && rs.nodeType == 3){
  239. // Text node, we have to split it.
  240. txt = rs.nodeValue;
  241. startNode = doc.createTextNode(txt.substring(0, range.startOffset));
  242. endNode = doc.createTextNode(txt.substring(range.startOffset));
  243. brNode = doc.createElement("br");
  244. if(endNode.nodeValue == "" && has("webkit")){
  245. endNode = doc.createTextNode('\xA0')
  246. }
  247. domConstruct.place(startNode, rs, "after");
  248. domConstruct.place(brNode, startNode, "after");
  249. domConstruct.place(endNode, brNode, "after");
  250. domConstruct.destroy(rs);
  251. newrange = rangeapi.create(this.editor.window);
  252. newrange.setStart(endNode,0);
  253. selection.removeAllRanges();
  254. selection.addRange(newrange);
  255. return false;
  256. }
  257. return true; // let browser handle
  258. }
  259. }else{
  260. selection = rangeapi.getSelection(this.editor.window);
  261. if(selection.rangeCount){
  262. range = selection.getRangeAt(0);
  263. if(range && range.startContainer){
  264. if(!range.collapsed){
  265. range.deleteContents();
  266. selection = rangeapi.getSelection(this.editor.window);
  267. range = selection.getRangeAt(0);
  268. }
  269. rs = range.startContainer;
  270. if(rs && rs.nodeType == 3){
  271. // Text node, we have to split it.
  272. var endEmpty = false;
  273. var offset = range.startOffset;
  274. if(rs.length < offset){
  275. //We are not splitting the right node, try to locate the correct one
  276. ret = this._adjustNodeAndOffset(rs, offset);
  277. rs = ret.node;
  278. offset = ret.offset;
  279. }
  280. txt = rs.nodeValue;
  281. startNode = doc.createTextNode(txt.substring(0, offset));
  282. endNode = doc.createTextNode(txt.substring(offset));
  283. brNode = doc.createElement("br");
  284. if(!endNode.length){
  285. endNode = doc.createTextNode('\xA0');
  286. endEmpty = true;
  287. }
  288. if(startNode.length){
  289. domConstruct.place(startNode, rs, "after");
  290. }else{
  291. startNode = rs;
  292. }
  293. domConstruct.place(brNode, startNode, "after");
  294. domConstruct.place(endNode, brNode, "after");
  295. domConstruct.destroy(rs);
  296. newrange = rangeapi.create(this.editor.window);
  297. newrange.setStart(endNode,0);
  298. newrange.setEnd(endNode, endNode.length);
  299. selection.removeAllRanges();
  300. selection.addRange(newrange);
  301. if(endEmpty && !has("webkit")){
  302. this.editor._sCall("remove", []);
  303. }else{
  304. this.editor._sCall("collapse", [true]);
  305. }
  306. }else{
  307. var targetNode;
  308. if(range.startOffset >= 0){
  309. targetNode = rs.childNodes[range.startOffset];
  310. }
  311. var brNode = doc.createElement("br");
  312. var endNode = doc.createTextNode('\xA0');
  313. if(!targetNode){
  314. rs.appendChild(brNode);
  315. rs.appendChild(endNode);
  316. }else{
  317. domConstruct.place(brNode, targetNode, "before");
  318. domConstruct.place(endNode, brNode, "after");
  319. }
  320. newrange = rangeapi.create(this.editor.window);
  321. newrange.setStart(endNode,0);
  322. newrange.setEnd(endNode, endNode.length);
  323. selection.removeAllRanges();
  324. selection.addRange(newrange);
  325. this.editor._sCall("collapse", [true]);
  326. }
  327. }
  328. }else{
  329. // don't change this: do not call this.execCommand, as that may have other logic in subclass
  330. RichText.prototype.execCommand.call(this.editor, 'inserthtml', '<br>');
  331. }
  332. }
  333. return false;
  334. }
  335. var _letBrowserHandle = true;
  336. // first remove selection
  337. selection = rangeapi.getSelection(this.editor.window);
  338. range = selection.getRangeAt(0);
  339. if(!range.collapsed){
  340. range.deleteContents();
  341. selection = rangeapi.getSelection(this.editor.window);
  342. range = selection.getRangeAt(0);
  343. }
  344. var block = rangeapi.getBlockAncestor(range.endContainer, null, this.editor.editNode);
  345. var blockNode = block.blockNode;
  346. // if this is under a LI or the parent of the blockNode is LI, just let browser to handle it
  347. if((this._checkListLater = (blockNode && (blockNode.nodeName == 'LI' || blockNode.parentNode.nodeName == 'LI')))){
  348. if(has("mozilla")){
  349. // press enter in middle of P may leave a trailing <br/>, let's remove it later
  350. this._pressedEnterInBlock = blockNode;
  351. }
  352. // if this li only contains spaces, set the content to empty so the browser will outdent this item
  353. if(/^(\s|&nbsp;|&#160;|\xA0|<span\b[^>]*\bclass=['"]Apple-style-span['"][^>]*>(\s|&nbsp;|&#160;|\xA0)<\/span>)?(<br>)?$/.test(blockNode.innerHTML)){
  354. // empty LI node
  355. blockNode.innerHTML = '';
  356. if(has("webkit")){ // WebKit tosses the range when innerHTML is reset
  357. newrange = rangeapi.create(this.editor.window);
  358. newrange.setStart(blockNode, 0);
  359. selection.removeAllRanges();
  360. selection.addRange(newrange);
  361. }
  362. this._checkListLater = false; // nothing to check since the browser handles outdent
  363. }
  364. return true;
  365. }
  366. // text node directly under body, let's wrap them in a node
  367. if(!block.blockNode || block.blockNode===this.editor.editNode){
  368. try{
  369. RichText.prototype.execCommand.call(this.editor, 'formatblock',this.blockNodeForEnter);
  370. }catch(e2){ /*squelch FF3 exception bug when editor content is a single BR*/ }
  371. // get the newly created block node
  372. // FIXME
  373. block = {blockNode: this.editor._sCall('getAncestorElement', [this.blockNodeForEnter]),
  374. blockContainer: this.editor.editNode};
  375. if(block.blockNode){
  376. if(block.blockNode != this.editor.editNode &&
  377. (!(block.blockNode.textContent || block.blockNode.innerHTML).replace(/^\s+|\s+$/g, "").length)){
  378. this.removeTrailingBr(block.blockNode);
  379. return false;
  380. }
  381. }else{ // we shouldn't be here if formatblock worked
  382. block.blockNode = this.editor.editNode;
  383. }
  384. selection = rangeapi.getSelection(this.editor.window);
  385. range = selection.getRangeAt(0);
  386. }
  387. var newblock = doc.createElement(this.blockNodeForEnter);
  388. newblock.innerHTML=this.bogusHtmlContent;
  389. this.removeTrailingBr(block.blockNode);
  390. var endOffset = range.endOffset;
  391. var node = range.endContainer;
  392. if(node.length < endOffset){
  393. //We are not checking the right node, try to locate the correct one
  394. var ret = this._adjustNodeAndOffset(node, endOffset);
  395. node = ret.node;
  396. endOffset = ret.offset;
  397. }
  398. if(rangeapi.atEndOfContainer(block.blockNode, node, endOffset)){
  399. if(block.blockNode === block.blockContainer){
  400. block.blockNode.appendChild(newblock);
  401. }else{
  402. domConstruct.place(newblock, block.blockNode, "after");
  403. }
  404. _letBrowserHandle = false;
  405. // lets move caret to the newly created block
  406. newrange = rangeapi.create(this.editor.window);
  407. newrange.setStart(newblock, 0);
  408. selection.removeAllRanges();
  409. selection.addRange(newrange);
  410. if(this.editor.height){
  411. winUtils.scrollIntoView(newblock);
  412. }
  413. }else if(rangeapi.atBeginningOfContainer(block.blockNode,
  414. range.startContainer, range.startOffset)){
  415. domConstruct.place(newblock, block.blockNode, block.blockNode === block.blockContainer ? "first" : "before");
  416. if(newblock.nextSibling && this.editor.height){
  417. // position input caret - mostly WebKit needs this
  418. newrange = rangeapi.create(this.editor.window);
  419. newrange.setStart(newblock.nextSibling, 0);
  420. selection.removeAllRanges();
  421. selection.addRange(newrange);
  422. // browser does not scroll the caret position into view, do it manually
  423. winUtils.scrollIntoView(newblock.nextSibling);
  424. }
  425. _letBrowserHandle = false;
  426. }else{ //press enter in the middle of P/DIV/Whatever/
  427. if(block.blockNode === block.blockContainer){
  428. block.blockNode.appendChild(newblock);
  429. }else{
  430. domConstruct.place(newblock, block.blockNode, "after");
  431. }
  432. _letBrowserHandle = false;
  433. // Clone any block level styles.
  434. if(block.blockNode.style){
  435. if(newblock.style){
  436. if(block.blockNode.style.cssText){
  437. newblock.style.cssText = block.blockNode.style.cssText;
  438. }
  439. }
  440. }
  441. // Okay, we probably have to split.
  442. rs = range.startContainer;
  443. var firstNodeMoved;
  444. if(rs && rs.nodeType == 3){
  445. // Text node, we have to split it.
  446. var nodeToMove, tNode;
  447. endOffset = range.endOffset;
  448. if(rs.length < endOffset){
  449. //We are not splitting the right node, try to locate the correct one
  450. ret = this._adjustNodeAndOffset(rs, endOffset);
  451. rs = ret.node;
  452. endOffset = ret.offset;
  453. }
  454. txt = rs.nodeValue;
  455. startNode = doc.createTextNode(txt.substring(0, endOffset));
  456. endNode = doc.createTextNode(txt.substring(endOffset, txt.length));
  457. // Place the split, then remove original nodes.
  458. domConstruct.place(startNode, rs, "before");
  459. domConstruct.place(endNode, rs, "after");
  460. domConstruct.destroy(rs);
  461. // Okay, we split the text. Now we need to see if we're
  462. // parented to the block element we're splitting and if
  463. // not, we have to split all the way up. Ugh.
  464. var parentC = startNode.parentNode;
  465. while(parentC !== block.blockNode){
  466. var tg = parentC.tagName;
  467. var newTg = doc.createElement(tg);
  468. // Clone over any 'style' data.
  469. if(parentC.style){
  470. if(newTg.style){
  471. if(parentC.style.cssText){
  472. newTg.style.cssText = parentC.style.cssText;
  473. }
  474. }
  475. }
  476. // If font also need to clone over any font data.
  477. if(parentC.tagName === "FONT"){
  478. if(parentC.color){
  479. newTg.color = parentC.color;
  480. }
  481. if(parentC.face){
  482. newTg.face = parentC.face;
  483. }
  484. if(parentC.size){ // this check was necessary on IE
  485. newTg.size = parentC.size;
  486. }
  487. }
  488. nodeToMove = endNode;
  489. while(nodeToMove){
  490. tNode = nodeToMove.nextSibling;
  491. newTg.appendChild(nodeToMove);
  492. nodeToMove = tNode;
  493. }
  494. domConstruct.place(newTg, parentC, "after");
  495. startNode = parentC;
  496. endNode = newTg;
  497. parentC = parentC.parentNode;
  498. }
  499. // Lastly, move the split out tags to the new block.
  500. // as they should now be split properly.
  501. nodeToMove = endNode;
  502. if(nodeToMove.nodeType == 1 || (nodeToMove.nodeType == 3 && nodeToMove.nodeValue)){
  503. // Non-blank text and non-text nodes need to clear out that blank space
  504. // before moving the contents.
  505. newblock.innerHTML = "";
  506. }
  507. firstNodeMoved = nodeToMove;
  508. while(nodeToMove){
  509. tNode = nodeToMove.nextSibling;
  510. newblock.appendChild(nodeToMove);
  511. nodeToMove = tNode;
  512. }
  513. }
  514. //lets move caret to the newly created block
  515. newrange = rangeapi.create(this.editor.window);
  516. var nodeForCursor;
  517. var innerMostFirstNodeMoved = firstNodeMoved;
  518. if(this.blockNodeForEnter !== 'BR'){
  519. while(innerMostFirstNodeMoved){
  520. nodeForCursor = innerMostFirstNodeMoved;
  521. tNode = innerMostFirstNodeMoved.firstChild;
  522. innerMostFirstNodeMoved = tNode;
  523. }
  524. if(nodeForCursor && nodeForCursor.parentNode){
  525. newblock = nodeForCursor.parentNode;
  526. newrange.setStart(newblock, 0);
  527. selection.removeAllRanges();
  528. selection.addRange(newrange);
  529. if(this.editor.height){
  530. winUtils.scrollIntoView(newblock);
  531. }
  532. if(has("mozilla")){
  533. // press enter in middle of P may leave a trailing <br/>, let's remove it later
  534. this._pressedEnterInBlock = block.blockNode;
  535. }
  536. }else{
  537. _letBrowserHandle = true;
  538. }
  539. }else{
  540. newrange.setStart(newblock, 0);
  541. selection.removeAllRanges();
  542. selection.addRange(newrange);
  543. if(this.editor.height){
  544. winUtils.scrollIntoView(newblock);
  545. }
  546. if(has("mozilla")){
  547. // press enter in middle of P may leave a trailing <br/>, let's remove it later
  548. this._pressedEnterInBlock = block.blockNode;
  549. }
  550. }
  551. }
  552. return _letBrowserHandle;
  553. },
  554. _adjustNodeAndOffset: function(/*DomNode*/node, /*Int*/offset){
  555. // summary:
  556. // In the case there are multiple text nodes in a row the offset may not be within the node. If the offset is larger than the node length, it will attempt to find
  557. // the next text sibling until it locates the text node in which the offset refers to
  558. // node:
  559. // The node to check.
  560. // offset:
  561. // The position to find within the text node
  562. // tags:
  563. // private.
  564. while(node.length < offset && node.nextSibling && node.nextSibling.nodeType==3){
  565. //Adjust the offset and node in the case of multiple text nodes in a row
  566. offset = offset - node.length;
  567. node = node.nextSibling;
  568. }
  569. return {"node": node, "offset": offset};
  570. },
  571. removeTrailingBr: function(container){
  572. // summary:
  573. // If last child of container is a `<br>`, then remove it.
  574. // tags:
  575. // private
  576. var para = /P|DIV|LI/i.test(container.tagName) ?
  577. container : this.editor._sCall("getParentOfType", [container,['P','DIV','LI']]);
  578. if(!para){ return; }
  579. if(para.lastChild){
  580. if((para.childNodes.length > 1 && para.lastChild.nodeType == 3 && /^[\s\xAD]*$/.test(para.lastChild.nodeValue)) ||
  581. para.lastChild.tagName=='BR'){
  582. domConstruct.destroy(para.lastChild);
  583. }
  584. }
  585. if(!para.childNodes.length){
  586. para.innerHTML=this.bogusHtmlContent;
  587. }
  588. }
  589. });
  590. });