NormalizeIndentOutdent.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882
  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["dojox.editor.plugins.NormalizeIndentOutdent"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.editor.plugins.NormalizeIndentOutdent"] = true;
  8. dojo.provide("dojox.editor.plugins.NormalizeIndentOutdent");
  9. dojo.require("dijit._editor.selection");
  10. dojo.require("dijit._editor._Plugin");
  11. dojo.declare("dojox.editor.plugins.NormalizeIndentOutdent",dijit._editor._Plugin,{
  12. // summary:
  13. // This plugin provides improved indent and outdent handling to
  14. // the editor. It tries to generate valid HTML, as well as be
  15. // consistent about how it indents and outdents lists and blocks/elements.
  16. // indentBy: [public] number
  17. // The amount to indent by. Valid values are 1+. This is combined with
  18. // the indentUnits parameter to determine how much to indent or outdent
  19. // by for regular text. It does not affect lists.
  20. indentBy: 40,
  21. // indentUnits: [public] String
  22. // The units to apply to the indent amount. Usually 'px', but can also
  23. // be em.
  24. indentUnits: "px",
  25. setEditor: function(editor){
  26. // summary:
  27. // Over-ride for the setting of the editor.
  28. // editor: Object
  29. // The editor to configure for this plugin to use.
  30. this.editor = editor;
  31. // Register out indent handler via the builtin over-ride mechanism.
  32. editor._indentImpl = dojo.hitch(this, this._indentImpl);
  33. editor._outdentImpl = dojo.hitch(this, this._outdentImpl);
  34. // Take over the query command enabled function, we want to prevent
  35. // indent of first items in a list, etc.
  36. if(!editor._indentoutdent_queryCommandEnabled){
  37. editor._indentoutdent_queryCommandEnabled = editor.queryCommandEnabled;
  38. }
  39. editor.queryCommandEnabled = dojo.hitch(this, this._queryCommandEnabled);
  40. // We need the custom undo code since we manipulate the dom
  41. // outside of the browser natives and only customUndo really handles
  42. // that. It will incur a performance hit, but should hopefully be
  43. // relatively small.
  44. editor.customUndo = true;
  45. },
  46. _queryCommandEnabled: function(command){
  47. // summary:
  48. // An over-ride for the editor's query command enabled,
  49. // so that we can prevent indents, etc, on bad elements
  50. // or positions (like first element in a list).
  51. // command:
  52. // The command passed in to check enablement.
  53. // tags:
  54. // private
  55. var c = command.toLowerCase();
  56. var ed, sel, range, node, tag, prevNode;
  57. var style = "marginLeft";
  58. if(!this._isLtr()){
  59. style = "marginRight";
  60. }
  61. if(c === "indent"){
  62. ed = this.editor;
  63. sel = dijit.range.getSelection(ed.window);
  64. if(sel && sel.rangeCount > 0){
  65. range = sel.getRangeAt(0);
  66. node = range.startContainer;
  67. // Check for li nodes first, we handle them a certain way.
  68. while(node && node !== ed.document && node !== ed.editNode){
  69. tag = this._getTagName(node);
  70. if(tag === "li"){
  71. prevNode = node.previousSibling;
  72. while(prevNode && prevNode.nodeType !== 1){
  73. prevNode = prevNode.previousSibling;
  74. }
  75. if(prevNode && this._getTagName(prevNode) === "li"){
  76. return true;
  77. }else{
  78. // First item, disallow
  79. return false;
  80. }
  81. }else if(this._isIndentableElement(tag)){
  82. return true;
  83. }
  84. node = node.parentNode;
  85. }
  86. if(this._isRootInline(range.startContainer)){
  87. return true;
  88. }
  89. }
  90. }else if(c === "outdent"){
  91. ed = this.editor;
  92. sel = dijit.range.getSelection(ed.window);
  93. if(sel && sel.rangeCount > 0){
  94. range = sel.getRangeAt(0);
  95. node = range.startContainer;
  96. // Check for li nodes first, we handle them a certain way.
  97. while(node && node !== ed.document && node !== ed.editNode){
  98. tag = this._getTagName(node);
  99. if(tag === "li"){
  100. // Standard list, we can ask the browser.
  101. return this.editor._indentoutdent_queryCommandEnabled(command);
  102. }else if(this._isIndentableElement(tag)){
  103. // Block, we need to handle the indent check.
  104. var cIndent = node.style?node.style[style]:"";
  105. if(cIndent){
  106. cIndent = this._convertIndent(cIndent);
  107. if(cIndent/this.indentBy >= 1){
  108. return true;
  109. }
  110. }
  111. return false;
  112. }
  113. node = node.parentNode;
  114. }
  115. if(this._isRootInline(range.startContainer)){
  116. return false;
  117. }
  118. }
  119. }else{
  120. return this.editor._indentoutdent_queryCommandEnabled(command);
  121. }
  122. return false;
  123. },
  124. _indentImpl: function(/*String*/ html) {
  125. // summary:
  126. // Improved implementation of indent, generates correct indent for
  127. // ul/ol
  128. var ed = this.editor;
  129. var sel = dijit.range.getSelection(ed.window);
  130. if(sel && sel.rangeCount > 0){
  131. var range = sel.getRangeAt(0);
  132. var node = range.startContainer;
  133. var tag, start, end, div;
  134. if(range.startContainer === range.endContainer){
  135. // No selection, just cursor point, we need to see if we're
  136. // in an indentable block, or similar.
  137. if(this._isRootInline(range.startContainer)){
  138. // Text at the 'root' of the document,
  139. // we'll try to indent it and all inline selements around it
  140. // as they are visually a single line.
  141. // First, we need to find the toplevel inline element that is rooted
  142. // to the document 'editNode'
  143. start = range.startContainer;
  144. while(start && start.parentNode !== ed.editNode){
  145. start = start.parentNode;
  146. }
  147. // Now we need to walk up its siblings and look for the first one in the rooting
  148. // that isn't inline or text, as we want to grab all of that for indent.
  149. while(start && start.previousSibling && (
  150. this._isTextElement(start) ||
  151. (start.nodeType === 1 && this._isInlineFormat(this._getTagName(start))
  152. ))){
  153. start = start.previousSibling;
  154. }
  155. if(start && start.nodeType === 1 && !this._isInlineFormat(this._getTagName(start))){
  156. // Adjust slightly, we're one node too far back in this case.
  157. start = start.nextSibling;
  158. }
  159. // Okay, we have a configured start, lets grab everything following it that's
  160. // inline and make it an indentable block!
  161. if(start){
  162. div = ed.document.createElement("div");
  163. dojo.place(div, start, "after");
  164. div.appendChild(start);
  165. end = div.nextSibling;
  166. while(end && (
  167. this._isTextElement(end) ||
  168. (end.nodeType === 1 &&
  169. this._isInlineFormat(this._getTagName(end)))
  170. )){
  171. // Add it.
  172. div.appendChild(end);
  173. end = div.nextSibling;
  174. }
  175. this._indentElement(div);
  176. dojo.withGlobal(ed.window,
  177. "selectElementChildren", dijit._editor.selection, [div]);
  178. dojo.withGlobal(ed.window,
  179. "collapse", dijit._editor.selection, [true]);
  180. }
  181. }else{
  182. while(node && node !== ed.document && node !== ed.editNode){
  183. tag = this._getTagName(node);
  184. if(tag === "li"){
  185. this._indentList(node);
  186. return;
  187. }else if(this._isIndentableElement(tag)){
  188. this._indentElement(node);
  189. return;
  190. }
  191. node = node.parentNode;
  192. }
  193. }
  194. }else{
  195. var curNode;
  196. // multi-node select. We need to scan over them.
  197. // Find the two containing nodes at start and end.
  198. // then move the end one node past. Then ... lets see
  199. // what we can indent!
  200. start = range.startContainer;
  201. end = range.endContainer;
  202. // Find the non-text nodes.
  203. while(start && this._isTextElement(start) && start.parentNode !== ed.editNode){
  204. start = start.parentNode;
  205. }
  206. while(end && this._isTextElement(end) && end.parentNode !== ed.editNode){
  207. end = end.parentNode;
  208. }
  209. if(end === ed.editNode || end === ed.document.body){
  210. // Okay, selection end is somewhere after start, we need to find the last node
  211. // that is safely in the range.
  212. curNode = start;
  213. while(curNode.nextSibling &&
  214. dojo.withGlobal(ed.window, "inSelection", dijit._editor.selection, [curNode])){
  215. curNode = curNode.nextSibling;
  216. }
  217. end = curNode;
  218. if(end === ed.editNode || end === ed.document.body){
  219. // Unable to determine real selection end, so just make it
  220. // a single node indent of start + all following inline styles, if
  221. // present, then just exit.
  222. tag = this._getTagName(start);
  223. if(tag === "li"){
  224. this._indentList(start);
  225. }else if(this._isIndentableElement(tag)){
  226. this._indentElement(start);
  227. }else if(this._isTextElement(start) ||
  228. this._isInlineFormat(tag)){
  229. // inline element or textnode, So we want to indent it somehow
  230. div = ed.document.createElement("div");
  231. dojo.place(div, start, "after");
  232. // Find and move all inline tags following the one we inserted also into the
  233. // div so we don't split up content funny.
  234. var next = start;
  235. while(next && (
  236. this._isTextElement(next) ||
  237. (next.nodeType === 1 &&
  238. this._isInlineFormat(this._getTagName(next))))){
  239. div.appendChild(next);
  240. next = div.nextSibling;
  241. }
  242. this._indentElement(div);
  243. }
  244. return;
  245. }
  246. }
  247. // Has a definite end somewhere, so lets try to indent up to it.
  248. // requires looking at the selections and in some cases, moving nodes
  249. // into indentable blocks.
  250. end = end.nextSibling;
  251. curNode = start;
  252. while(curNode && curNode !== end){
  253. if(curNode.nodeType === 1){
  254. tag = this._getTagName(curNode);
  255. if(dojo.isIE){
  256. // IE sometimes inserts blank P tags, which we want to skip
  257. // as they end up indented, which messes up layout.
  258. if(tag === "p" && this._isEmpty(curNode)){
  259. curNode = curNode.nextSibling;
  260. continue;
  261. }
  262. }
  263. if(tag === "li"){
  264. if(div){
  265. if(this._isEmpty(div)){
  266. div.parentNode.removeChild(div);
  267. }else{
  268. this._indentElement(div);
  269. }
  270. div = null;
  271. }
  272. this._indentList(curNode);
  273. }else if(!this._isInlineFormat(tag) && this._isIndentableElement(tag)){
  274. if(div){
  275. if(this._isEmpty(div)){
  276. div.parentNode.removeChild(div);
  277. }else{
  278. this._indentElement(div);
  279. }
  280. div = null;
  281. }
  282. curNode = this._indentElement(curNode);
  283. }else if(this._isInlineFormat(tag)){
  284. // inline tag.
  285. if(!div){
  286. div = ed.document.createElement("div");
  287. dojo.place(div, curNode, "after");
  288. div.appendChild(curNode);
  289. curNode = div;
  290. }else{
  291. div.appendChild(curNode);
  292. curNode = div;
  293. }
  294. }
  295. }else if(this._isTextElement(curNode)){
  296. if(!div){
  297. div = ed.document.createElement("div");
  298. dojo.place(div, curNode, "after");
  299. div.appendChild(curNode);
  300. curNode = div;
  301. }else{
  302. div.appendChild(curNode);
  303. curNode = div;
  304. }
  305. }
  306. curNode = curNode.nextSibling;
  307. }
  308. // Okay, indent everything we merged if we haven't yet..
  309. if(div){
  310. if(this._isEmpty(div)){
  311. div.parentNode.removeChild(div);
  312. }else{
  313. this._indentElement(div);
  314. }
  315. div = null;
  316. }
  317. }
  318. }
  319. },
  320. _indentElement: function(node){
  321. // summary:
  322. // Function to indent a block type tag.
  323. // node:
  324. // The node who's content to indent.
  325. // tags:
  326. // private
  327. var style = "marginLeft";
  328. if(!this._isLtr()){
  329. style = "marginRight";
  330. }
  331. var tag = this._getTagName(node);
  332. if(tag === "ul" || tag === "ol"){
  333. // Lists indent funny, so lets wrap them in a div
  334. // and indent the div instead.
  335. var div = this.editor.document.createElement("div");
  336. dojo.place(div, node, "after");
  337. div.appendChild(node);
  338. node = div;
  339. }
  340. var cIndent = node.style?node.style[style]:"";
  341. if(cIndent){
  342. cIndent = this._convertIndent(cIndent);
  343. cIndent = (parseInt(cIndent, 10) + this.indentBy) + this.indentUnits;
  344. }else{
  345. cIndent = this.indentBy + this.indentUnits;
  346. }
  347. dojo.style(node, style, cIndent);
  348. return node; //Return the node that was indented.
  349. },
  350. _outdentElement: function(node){
  351. // summary:
  352. // Function to outdent a block type tag.
  353. // node:
  354. // The node who's content to outdent.
  355. // tags:
  356. // private
  357. var style = "marginLeft";
  358. if(!this._isLtr()){
  359. style = "marginRight";
  360. }
  361. var cIndent = node.style?node.style[style]:"";
  362. if(cIndent){
  363. cIndent = this._convertIndent(cIndent);
  364. if(cIndent - this.indentBy > 0){
  365. cIndent = (parseInt(cIndent, 10) - this.indentBy) + this.indentUnits;
  366. }else{
  367. cIndent = "";
  368. }
  369. dojo.style(node, style, cIndent);
  370. }
  371. },
  372. _outdentImpl: function(/*String*/ html) {
  373. // summary:
  374. // Improved implementation of outdent, generates correct indent for
  375. // ul/ol and other elements.
  376. // tags:
  377. // private
  378. var ed = this.editor;
  379. var sel = dijit.range.getSelection(ed.window);
  380. if(sel && sel.rangeCount > 0){
  381. var range = sel.getRangeAt(0);
  382. var node = range.startContainer;
  383. var tag;
  384. if(range.startContainer === range.endContainer){
  385. // Check for li nodes first, we handle them a certain way.
  386. while(node && node !== ed.document && node !== ed.editNode){
  387. tag = this._getTagName(node);
  388. if(tag === "li"){
  389. return this._outdentList(node);
  390. }else if(this._isIndentableElement(tag)){
  391. return this._outdentElement(node);
  392. }
  393. node = node.parentNode;
  394. }
  395. ed.document.execCommand("outdent", false, html);
  396. }else{
  397. // multi-node select. We need to scan over them.
  398. // Find the two containing nodes at start and end.
  399. // then move the end one node past. Then ... lets see
  400. // what we can outdent!
  401. var start = range.startContainer;
  402. var end = range.endContainer;
  403. // Find the non-text nodes.
  404. while(start && start.nodeType === 3){
  405. start = start.parentNode;
  406. }
  407. while(end && end.nodeType === 3){
  408. end = end.parentNode;
  409. }
  410. end = end.nextSibling;
  411. var curNode = start;
  412. while(curNode && curNode !== end){
  413. if(curNode.nodeType === 1){
  414. tag = this._getTagName(curNode);
  415. if(tag === "li"){
  416. this._outdentList(curNode);
  417. }else if(this._isIndentableElement(tag)){
  418. this._outdentElement(curNode);
  419. }
  420. }
  421. curNode = curNode.nextSibling;
  422. }
  423. }
  424. }
  425. return null;
  426. },
  427. _indentList: function(listItem){
  428. // summary:
  429. // Internal function to handle indenting a list element.
  430. // listItem:
  431. // The list item to indent.
  432. // tags:
  433. // private
  434. var ed = this.editor;
  435. var newList, li;
  436. var listContainer = listItem.parentNode;
  437. var prevTag = listItem.previousSibling;
  438. // Ignore text, we want elements.
  439. while(prevTag && prevTag.nodeType !== 1){
  440. prevTag = prevTag.previousSibling;
  441. }
  442. var type = null;
  443. var tg = this._getTagName(listContainer);
  444. // Try to determine what kind of list item is here to indent.
  445. if(tg === "ol"){
  446. type = "ol";
  447. }else if(tg === "ul"){
  448. type = "ul";
  449. }
  450. // Only indent list items actually in a list.
  451. // Bail out if the list is malformed somehow.
  452. if(type){
  453. // There is a previous node in the list, so we want to append a new list
  454. // element after it that contains a new list of the content to indent it.
  455. if(prevTag && prevTag.tagName.toLowerCase() == "li"){
  456. // Lets see if we can merge this into another (Eg,
  457. // does the sibling li contain an embedded list already of
  458. // the same type? if so, we move into that one.
  459. var embList;
  460. if(prevTag.childNodes){
  461. var i;
  462. for(i = 0; i < prevTag.childNodes.length; i++){
  463. var n = prevTag.childNodes[i];
  464. if(n.nodeType === 3){
  465. if(dojo.trim(n.nodeValue)){
  466. if(embList){
  467. // Non-empty text after list, exit, can't embed.
  468. break;
  469. }
  470. }
  471. }else if(n.nodeType === 1 && !embList){
  472. // See if this is a list container.
  473. if(type === n.tagName.toLowerCase()){
  474. embList = n;
  475. }
  476. }else{
  477. // Other node present, break, can't embed.
  478. break;
  479. }
  480. }
  481. }
  482. if(embList){
  483. // We found a list to merge to, so merge.
  484. embList.appendChild(listItem);
  485. }else{
  486. // Nope, wasn't an embedded list container,
  487. // So lets just create a new one.
  488. newList = ed.document.createElement(type);
  489. dojo.style(newList, {
  490. paddingTop: "0px",
  491. paddingBottom: "0px"
  492. });
  493. li = ed.document.createElement("li");
  494. dojo.style(li, {
  495. listStyleImage: "none",
  496. listStyleType: "none"
  497. });
  498. prevTag.appendChild(newList);
  499. newList.appendChild(listItem);
  500. }
  501. // Move cursor.
  502. dojo.withGlobal(ed.window,
  503. "selectElementChildren", dijit._editor.selection, [listItem]);
  504. dojo.withGlobal(ed.window,
  505. "collapse", dijit._editor.selection, [true]);
  506. }
  507. }
  508. },
  509. _outdentList: function(listItem){
  510. // summary:
  511. // Internal function to handle outdenting a list element.
  512. // listItem:
  513. // The list item to outdent.
  514. // tags:
  515. // private
  516. var ed = this.editor;
  517. var list = listItem.parentNode;
  518. var type = null;
  519. var tg = list.tagName ? list.tagName.toLowerCase() : "";
  520. var li;
  521. // Try to determine what kind of list contains the item.
  522. if(tg === "ol"){
  523. type = "ol";
  524. }else if(tg === "ul"){
  525. type = "ul";
  526. }
  527. // Check to see if it is a nested list, as outdenting is handled differently.
  528. var listParent = list.parentNode;
  529. var lpTg = this._getTagName(listParent);
  530. // We're in a list, so we need to outdent this specially.
  531. // Check for welformed and malformed lists (<ul><ul></ul>U/ul> type stuff).
  532. if(lpTg === "li" || lpTg === "ol" || lpTg === "ul"){
  533. if(lpTg === "ol" || lpTg === "ul"){
  534. // Okay, we need to fix this up, this is invalid html,
  535. // So try to combine this into a previous element before
  536. // de do a shuffle of the nodes, to build an HTML compliant
  537. // list.
  538. var prevListLi = list.previousSibling;
  539. while(prevListLi && (prevListLi.nodeType !== 1 ||
  540. (prevListLi.nodeType === 1 &&
  541. this._getTagName(prevListLi) !== "li"))
  542. ){
  543. prevListLi = prevListLi.previousSibling;
  544. }
  545. if(prevListLi){
  546. // Move this list up into the previous li
  547. // to fix malformation.
  548. prevListLi.appendChild(list);
  549. listParent = prevListLi;
  550. }else{
  551. li = listItem;
  552. var firstItem = listItem;
  553. while(li.previousSibling){
  554. li = li.previousSibling;
  555. if(li.nodeType === 1 && this._getTagName(li) === "li"){
  556. firstItem = li;
  557. }
  558. }
  559. if(firstItem !== listItem){
  560. dojo.place(firstItem, list, "before");
  561. firstItem.appendChild(list);
  562. listParent = firstItem;
  563. }else{
  564. // No previous list item in a malformed list
  565. // ... so create one and move into that.
  566. li = ed.document.createElement("li");
  567. dojo.place(li, list, "before");
  568. li.appendChild(list);
  569. listParent = li;
  570. }
  571. dojo.style(list, {
  572. paddingTop: "0px",
  573. paddingBottom: "0px"
  574. });
  575. }
  576. }
  577. // find the previous node, if any,
  578. // non-text.
  579. var prevLi = listItem.previousSibling;
  580. while(prevLi && prevLi.nodeType !== 1){
  581. prevLi = prevLi.previousSibling;
  582. }
  583. var nextLi = listItem.nextSibling;
  584. while(nextLi && nextLi.nodeType !== 1){
  585. nextLi = nextLi.nextSibling;
  586. }
  587. if(!prevLi){
  588. // Top item in a nested list, so just move it out
  589. // and then shuffle the remaining indented list into it.
  590. dojo.place(listItem, listParent, "after");
  591. listItem.appendChild(list);
  592. }else if(!nextLi){
  593. // Last item in a nested list, shuffle it out after
  594. // the nsted list only.
  595. dojo.place(listItem, listParent, "after");
  596. }else{
  597. // Item is in the middle of an embedded list, so we
  598. // have to split it.
  599. // Move all the items following current list item into
  600. // a list after it.
  601. var newList = ed.document.createElement(type);
  602. dojo.style(newList, {
  603. paddingTop: "0px",
  604. paddingBottom: "0px"
  605. });
  606. listItem.appendChild(newList);
  607. while(listItem.nextSibling){
  608. newList.appendChild(listItem.nextSibling);
  609. }
  610. // Okay, now place the list item after the
  611. // current list parent (li).
  612. dojo.place(listItem, listParent, "after");
  613. }
  614. // Clean up any empty lists left behind.
  615. if(list && this._isEmpty(list)){
  616. list.parentNode.removeChild(list);
  617. }
  618. if(listParent && this._isEmpty(listParent)){
  619. listParent.parentNode.removeChild(listParent);
  620. }
  621. // Move our cursor to the list item we moved.
  622. dojo.withGlobal(ed.window,
  623. "selectElementChildren", dijit._editor.selection, [listItem]);
  624. dojo.withGlobal(ed.window,
  625. "collapse", dijit._editor.selection, [true]);
  626. }else{
  627. // Not in a nested list, so we can just defer to the
  628. // browser and hope it outdents right.
  629. ed.document.execCommand("outdent", false, null);
  630. }
  631. },
  632. _isEmpty: function(node){
  633. // summary:
  634. // Internal function to determine if a node is 'empty'
  635. // Eg, contains only blank text. Used to determine if
  636. // an empty list element should be removed or not.
  637. // node:
  638. // The node to check.
  639. // tags:
  640. // private
  641. if(node.childNodes){
  642. var empty = true;
  643. var i;
  644. for(i = 0; i < node.childNodes.length; i++){
  645. var n = node.childNodes[i];
  646. if(n.nodeType === 1){
  647. if(this._getTagName(n) === "p"){
  648. if(!dojo.trim(n.innerHTML)){
  649. continue;
  650. }
  651. }
  652. empty = false;
  653. break;
  654. }else if(this._isTextElement(n)){
  655. // Check for empty text.
  656. var nv = dojo.trim(n.nodeValue);
  657. if(nv && nv !=="&nbsp;" && nv !== "\u00A0"){
  658. empty = false;
  659. break;
  660. }
  661. }else{
  662. empty = false;
  663. break;
  664. }
  665. }
  666. return empty;
  667. }else{
  668. return true;
  669. }
  670. },
  671. _isIndentableElement: function(tag){
  672. // summary:
  673. // Internal function to detect what element types
  674. // are indent-controllable by us.
  675. // tag:
  676. // The tag to check
  677. // tags:
  678. // private
  679. switch(tag){
  680. case "p":
  681. case "div":
  682. case "h1":
  683. case "h2":
  684. case "h3":
  685. case "center":
  686. case "table":
  687. case "ul":
  688. case "ol":
  689. return true;
  690. default:
  691. return false;
  692. }
  693. },
  694. _convertIndent: function(indent){
  695. // summary:
  696. // Function to convert the current indent style to
  697. // the units we're using by some heuristic.
  698. // indent:
  699. // The indent amount to convert.
  700. // tags:
  701. // private
  702. var pxPerEm = 12;
  703. indent = indent + "";
  704. indent = indent.toLowerCase();
  705. var curUnit = (indent.indexOf("px") > 0) ? "px" : (indent.indexOf("em") > 0) ? "em" : "px";
  706. indent = indent.replace(/(px;?|em;?)/gi, "");
  707. if(curUnit === "px"){
  708. if(this.indentUnits === "em"){
  709. indent = Math.ceil(indent/pxPerEm);
  710. }
  711. }else{
  712. if(this.indentUnits === "px"){
  713. indent = indent * pxPerEm;
  714. }
  715. }
  716. return indent;
  717. },
  718. _isLtr: function(){
  719. // summary:
  720. // Function to detect if the editor body is in RTL or LTR.
  721. // tags:
  722. // private
  723. var editDoc = this.editor.document.body;
  724. return dojo.withGlobal(this.editor.window, function(){
  725. var cs = dojo.getComputedStyle(editDoc);
  726. return cs ? cs.direction == "ltr" : true;
  727. });
  728. },
  729. _isInlineFormat: function(tag){
  730. // summary:
  731. // Function to determine if the current tag is an inline
  732. // element that does formatting, as we don't want to
  733. // break/indent around it, as it can screw up text.
  734. // tag:
  735. // The tag to examine
  736. // tags:
  737. // private
  738. switch(tag){
  739. case "a":
  740. case "b":
  741. case "strong":
  742. case "s":
  743. case "strike":
  744. case "i":
  745. case "u":
  746. case "em":
  747. case "sup":
  748. case "sub":
  749. case "span":
  750. case "font":
  751. case "big":
  752. case "cite":
  753. case "q":
  754. case "img":
  755. case "small":
  756. return true;
  757. default:
  758. return false;
  759. }
  760. },
  761. _getTagName: function(node){
  762. // summary:
  763. // Internal function to get the tag name of an element
  764. // if any.
  765. // node:
  766. // The node to look at.
  767. // tags:
  768. // private
  769. var tag = "";
  770. if(node && node.nodeType === 1){
  771. tag = node.tagName?node.tagName.toLowerCase():"";
  772. }
  773. return tag;
  774. },
  775. _isRootInline: function(node){
  776. // summary:
  777. // This functions tests whether an indicated node is in root as inline
  778. // or rooted inline elements in the page.
  779. // node:
  780. // The node to start at.
  781. // tags:
  782. // private
  783. var ed = this.editor;
  784. if(this._isTextElement(node) && node.parentNode === ed.editNode){
  785. return true;
  786. }else if(node.nodeType === 1 && this._isInlineFormat(node) && node.parentNode === ed.editNode){
  787. return true;
  788. }else if(this._isTextElement(node) && this._isInlineFormat(this._getTagName(node.parentNode))){
  789. node = node.parentNode;
  790. while(node && node !== ed.editNode && this._isInlineFormat(this._getTagName(node))){
  791. node = node.parentNode;
  792. }
  793. if(node === ed.editNode){
  794. return true;
  795. }
  796. }
  797. return false;
  798. },
  799. _isTextElement: function(node){
  800. // summary:
  801. // Helper function to check for text nodes.
  802. // node:
  803. // The node to check.
  804. // tags:
  805. // private
  806. if(node && node.nodeType === 3 || node.nodeType === 4){
  807. return true;
  808. }
  809. return false;
  810. }
  811. });
  812. // Register this plugin.
  813. dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){
  814. if(o.plugin){ return; }
  815. var name = o.args.name.toLowerCase();
  816. if(name === "normalizeindentoutdent"){
  817. o.plugin = new dojox.editor.plugins.NormalizeIndentOutdent({
  818. indentBy: ("indentBy" in o.args) ?
  819. (o.args.indentBy > 0 ? o.args.indentBy : 40) :
  820. 40,
  821. indentUnits: ("indentUnits" in o.args) ?
  822. (o.args.indentUnits.toLowerCase() == "em"? "em" : "px") :
  823. "px"
  824. });
  825. }
  826. });
  827. }