EnterKeyHandling.js 21 KB

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