EnterKeyHandling.js 22 KB

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