Blockquote.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  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.Blockquote"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.editor.plugins.Blockquote"] = true;
  8. dojo.provide("dojox.editor.plugins.Blockquote");
  9. dojo.require("dijit._editor._Plugin");
  10. dojo.require("dijit.form.Button");
  11. dojo.require("dojo.i18n");
  12. dojo.requireLocalization("dojox.editor.plugins", "Blockquote", null, "ROOT,ar,bg,ca,cs,da,de,el,es,fi,fr,he,hr,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw");
  13. dojo.declare("dojox.editor.plugins.Blockquote",dijit._editor._Plugin,{
  14. // summary:
  15. // This plugin provides Blockquote cabability to the editor.
  16. // window/tab
  17. // iconClassPrefix: [const] String
  18. // The CSS class name for the button node icon.
  19. iconClassPrefix: "dijitAdditionalEditorIcon",
  20. _initButton: function(){
  21. // summary:
  22. // Over-ride for creation of the preview button.
  23. this._nlsResources = dojo.i18n.getLocalization("dojox.editor.plugins", "Blockquote");
  24. this.button = new dijit.form.ToggleButton({
  25. label: this._nlsResources["blockquote"],
  26. showLabel: false,
  27. iconClass: this.iconClassPrefix + " " + this.iconClassPrefix + "Blockquote",
  28. tabIndex: "-1",
  29. onClick: dojo.hitch(this, "_toggleQuote")
  30. });
  31. },
  32. setEditor: function(editor){
  33. // summary:
  34. // Over-ride for the setting of the editor.
  35. // editor: Object
  36. // The editor to configure for this plugin to use.
  37. this.editor = editor;
  38. this._initButton();
  39. this.connect(this.editor, "onNormalizedDisplayChanged", "updateState");
  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. _toggleQuote: function(arg){
  47. // summary:
  48. // Function to trigger previewing of the editor document
  49. // tags:
  50. // private
  51. try{
  52. var ed = this.editor;
  53. ed.focus();
  54. var quoteIt = this.button.get("checked");
  55. var sel = dijit.range.getSelection(ed.window);
  56. var range, elem, start, end;
  57. if(sel && sel.rangeCount > 0){
  58. range = sel.getRangeAt(0);
  59. }
  60. if(range){
  61. ed.beginEditing();
  62. if(quoteIt){
  63. // Lets see what we've got as a selection...
  64. var bq, tag;
  65. if(range.startContainer === range.endContainer){
  66. // No selection, just cursor point, we need to see if we're
  67. // in an indentable block, or similar.
  68. if(this._isRootInline(range.startContainer)){
  69. // Text at the 'root' of the document, so we need to gather all of it.,
  70. // First, we need to find the toplevel inline element that is rooted
  71. // to the document 'editNode'
  72. start = range.startContainer;
  73. while(start && start.parentNode !== ed.editNode){
  74. start = start.parentNode;
  75. }
  76. // Now we need to walk up its siblings and look for the first one in the rooting
  77. // that isn't inline or text, as we want to grab all of that for indent.
  78. while(start && start.previousSibling && (
  79. this._isTextElement(start) ||
  80. (start.nodeType === 1 &&
  81. this._isInlineFormat(this._getTagName(start))
  82. ))){
  83. start = start.previousSibling;
  84. }
  85. if(start && start.nodeType === 1 &&
  86. !this._isInlineFormat(this._getTagName(start))){
  87. // Adjust slightly, we're one node too far back in this case.
  88. start = start.nextSibling;
  89. }
  90. // Okay, we have a configured start, lets grab everything following it that's
  91. // inline and make it part of the blockquote!
  92. if(start){
  93. bq = ed.document.createElement("blockquote");
  94. dojo.place(bq, start, "after");
  95. bq.appendChild(start);
  96. end = bq.nextSibling;
  97. while(end && (
  98. this._isTextElement(end) ||
  99. (end.nodeType === 1 &&
  100. this._isInlineFormat(this._getTagName(end)))
  101. )){
  102. // Add it.
  103. bq.appendChild(end);
  104. end = bq.nextSibling;
  105. }
  106. }
  107. }else{
  108. // Figure out what to do when not root inline....
  109. var node = range.startContainer;
  110. while ((this._isTextElement(node) ||
  111. this._isInlineFormat(this._getTagName(node))
  112. || this._getTagName(node) === "li") &&
  113. node !== ed.editNode && node !== ed.document.body){
  114. node = node.parentNode;
  115. }
  116. if(node !== ed.editNode && node !== node.ownerDocument.documentElement){
  117. bq = ed.document.createElement("blockquote");
  118. dojo.place(bq, node, "after");
  119. bq.appendChild(node);
  120. }
  121. }
  122. if(bq){
  123. dojo.withGlobal(ed.window,
  124. "selectElementChildren", dijit._editor.selection, [bq]);
  125. dojo.withGlobal(ed.window,
  126. "collapse", dijit._editor.selection, [true]);
  127. }
  128. }else{
  129. var curNode;
  130. // multi-node select. We need to scan over them.
  131. // Find the two containing nodes at start and end.
  132. // then move the end one node past. Then ... lets see
  133. // what we can blockquote!
  134. start = range.startContainer;
  135. end = range.endContainer;
  136. // Find the non-text nodes.
  137. while(start && this._isTextElement(start) && start.parentNode !== ed.editNode){
  138. start = start.parentNode;
  139. }
  140. // Try to find the end node. We have to check the selection junk
  141. curNode = start;
  142. while(curNode.nextSibling && dojo.withGlobal(ed.window,
  143. "inSelection", dijit._editor.selection, [curNode])){
  144. curNode = curNode.nextSibling;
  145. }
  146. end = curNode;
  147. if(end === ed.editNode || end === ed.document.body){
  148. // Unable to determine real selection end, so just make it
  149. // a single node indent of start + all following inline styles, if
  150. // present, then just exit.
  151. bq = ed.document.createElement("blockquote");
  152. dojo.place(bq, start, "after");
  153. tag = this._getTagName(start);
  154. if(this._isTextElement(start) || this._isInlineFormat(tag)){
  155. // inline element or textnode
  156. // Find and move all inline tags following the one we inserted also into the
  157. // blockquote so we don't split up content funny.
  158. var next = start;
  159. while(next && (
  160. this._isTextElement(next) ||
  161. (next.nodeType === 1 &&
  162. this._isInlineFormat(this._getTagName(next))))){
  163. bq.appendChild(next);
  164. next = bq.nextSibling;
  165. }
  166. }else{
  167. bq.appendChild(start);
  168. }
  169. return;
  170. }
  171. // Has a definite end somewhere, so lets try to blockquote up to it.
  172. // requires looking at the selections and in some cases, moving nodes
  173. // into separate blockquotes.
  174. end = end.nextSibling;
  175. curNode = start;
  176. while(curNode && curNode !== end){
  177. if(curNode.nodeType === 1){
  178. tag = this._getTagName(curNode);
  179. if(tag !== "br"){
  180. if(!window.getSelection){
  181. // IE sometimes inserts blank P tags, which we want to skip
  182. // as they end up blockquoted, which messes up layout.
  183. if(tag === "p" && this._isEmpty(curNode)){
  184. curNode = curNode.nextSibling;
  185. continue;
  186. }
  187. }
  188. if(this._isInlineFormat(tag)){
  189. // inline tag.
  190. if(!bq){
  191. bq = ed.document.createElement("blockquote");
  192. dojo.place(bq, curNode, "after");
  193. bq.appendChild(curNode);
  194. }else{
  195. bq.appendChild(curNode);
  196. }
  197. curNode = bq;
  198. }else{
  199. if(bq){
  200. if(this._isEmpty(bq)){
  201. bq.parentNode.removeChild(bq);
  202. }
  203. }
  204. bq = ed.document.createElement("blockquote");
  205. dojo.place(bq, curNode, "after");
  206. bq.appendChild(curNode);
  207. curNode = bq;
  208. }
  209. }
  210. }else if(this._isTextElement(curNode)){
  211. if(!bq){
  212. bq = ed.document.createElement("blockquote");
  213. dojo.place(bq, curNode, "after");
  214. bq.appendChild(curNode);
  215. }else{
  216. bq.appendChild(curNode);
  217. }
  218. curNode = bq;
  219. }
  220. curNode = curNode.nextSibling;
  221. }
  222. // Okay, check the last bq, remove it if no content.
  223. if(bq){
  224. if(this._isEmpty(bq)){
  225. bq.parentNode.removeChild(bq);
  226. }else{
  227. dojo.withGlobal(ed.window,
  228. "selectElementChildren", dijit._editor.selection, [bq]);
  229. dojo.withGlobal(ed.window,
  230. "collapse", dijit._editor.selection, [true]);
  231. }
  232. bq = null;
  233. }
  234. }
  235. }else{
  236. var found = false;
  237. if(range.startContainer === range.endContainer){
  238. elem = range.endContainer;
  239. // Okay, now see if we can find one of the formatting types we're in.
  240. while(elem && elem !== ed.editNode && elem !== ed.document.body){
  241. var tg = elem.tagName?elem.tagName.toLowerCase():"";
  242. if(tg === "blockquote"){
  243. found = true;
  244. break;
  245. }
  246. elem = elem.parentNode;
  247. }
  248. if(found){
  249. var lastChild;
  250. while(elem.firstChild){
  251. lastChild = elem.firstChild;
  252. dojo.place(lastChild, elem, "before");
  253. }
  254. elem.parentNode.removeChild(elem);
  255. if(lastChild){
  256. dojo.withGlobal(ed.window,
  257. "selectElementChildren", dijit._editor.selection, [lastChild]);
  258. dojo.withGlobal(ed.window,
  259. "collapse", dijit._editor.selection, [true]);
  260. }
  261. }
  262. }else{
  263. // Multi-select! Gotta find all the blockquotes contained within the selection area.
  264. start = range.startContainer;
  265. end = range.endContainer;
  266. while(start && this._isTextElement(start) && start.parentNode !== ed.editNode){
  267. start = start.parentNode;
  268. }
  269. var selectedNodes = [];
  270. var cNode = start;
  271. while(cNode && cNode.nextSibling && dojo.withGlobal(ed.window,
  272. "inSelection", dijit._editor.selection, [cNode])){
  273. if(cNode.parentNode && this._getTagName(cNode.parentNode) === "blockquote"){
  274. cNode = cNode.parentNode;
  275. }
  276. selectedNodes.push(cNode);
  277. cNode = cNode.nextSibling;
  278. }
  279. // Find all the blocknodes now that we know the selection area.
  280. var bnNodes = this._findBlockQuotes(selectedNodes);
  281. while(bnNodes.length){
  282. var bn = bnNodes.pop();
  283. if(bn.parentNode){
  284. // Make sure we haven't seen this before and removed it.
  285. while(bn.firstChild){
  286. dojo.place(bn.firstChild, bn, "before");
  287. }
  288. bn.parentNode.removeChild(bn);
  289. }
  290. }
  291. }
  292. }
  293. ed.endEditing();
  294. }
  295. ed.onNormalizedDisplayChanged();
  296. }catch(e){ /* Squelch */ }
  297. },
  298. updateState: function(){
  299. // summary:
  300. // Overrides _Plugin.updateState(). This controls whether or not the current
  301. // cursor position should toggle on the quote button or not.
  302. // tags:
  303. // protected
  304. var ed = this.editor;
  305. var disabled = this.get("disabled");
  306. if(!ed || !ed.isLoaded){ return; }
  307. if(this.button){
  308. this.button.set("disabled", disabled);
  309. if(disabled){
  310. return;
  311. }
  312. // Some browsers (WebKit) doesn't actually get the tag info right.
  313. // So ... lets check it manually.
  314. var elem;
  315. var found = false;
  316. // Try to find the ansestor element (and see if it is blockquote)
  317. var sel = dijit.range.getSelection(ed.window);
  318. if(sel && sel.rangeCount > 0){
  319. var range = sel.getRangeAt(0);
  320. if(range){
  321. elem = range.endContainer;
  322. }
  323. }
  324. // Okay, now see if we can find one of the formatting types we're in.
  325. while(elem && elem !== ed.editNode && elem !== ed.document){
  326. var tg = elem.tagName?elem.tagName.toLowerCase():"";
  327. if(tg === "blockquote"){
  328. found = true;
  329. break;
  330. }
  331. elem = elem.parentNode;
  332. }
  333. // toggle whether or not the current selection is blockquoted.
  334. this.button.set("checked", found);
  335. }
  336. },
  337. _findBlockQuotes: function(nodeList){
  338. // summary:
  339. // function to find a ll the blocknode elements in a collection of
  340. // nodes
  341. // nodeList:
  342. // The list of nodes.
  343. // tags:
  344. // private
  345. var bnList = [];
  346. if(nodeList){
  347. var i;
  348. for(i = 0; i < nodeList.length; i++){
  349. var node = nodeList[i];
  350. if(node.nodeType === 1){
  351. if(this._getTagName(node) === "blockquote"){
  352. bnList.push(node);
  353. }
  354. if(node.childNodes && node.childNodes.length > 0){
  355. bnList = bnList.concat(this._findBlockQuotes(node.childNodes));
  356. }
  357. }
  358. }
  359. }
  360. return bnList;
  361. },
  362. /*****************************************************************/
  363. /* Functions borrowed from NormalizeIndentOutdent */
  364. /*****************************************************************/
  365. _getTagName: function(node){
  366. // summary:
  367. // Internal function to get the tag name of an element
  368. // if any.
  369. // node:
  370. // The node to look at.
  371. // tags:
  372. // private
  373. var tag = "";
  374. if(node && node.nodeType === 1){
  375. tag = node.tagName?node.tagName.toLowerCase():"";
  376. }
  377. return tag;
  378. },
  379. _isRootInline: function(node){
  380. // summary:
  381. // This functions tests whether an indicated node is in root as inline
  382. // or rooted inline elements in the page.
  383. // node:
  384. // The node to start at.
  385. // tags:
  386. // private
  387. var ed = this.editor;
  388. if(this._isTextElement(node) && node.parentNode === ed.editNode){
  389. return true;
  390. }else if(node.nodeType === 1 && this._isInlineFormat(node) && node.parentNode === ed.editNode){
  391. return true;
  392. }else if(this._isTextElement(node) && this._isInlineFormat(this._getTagName(node.parentNode))){
  393. node = node.parentNode;
  394. while(node && node !== ed.editNode && this._isInlineFormat(this._getTagName(node))){
  395. node = node.parentNode;
  396. }
  397. if(node === ed.editNode){
  398. return true;
  399. }
  400. }
  401. return false;
  402. },
  403. _isTextElement: function(node){
  404. // summary:
  405. // Helper function to check for text nodes.
  406. // node:
  407. // The node to check.
  408. // tags:
  409. // private
  410. if(node && node.nodeType === 3 || node.nodeType === 4){
  411. return true;
  412. }
  413. return false;
  414. },
  415. _isEmpty: function(node){
  416. // summary:
  417. // Internal function to determine if a node is 'empty'
  418. // Eg, contains only blank text. Used to determine if
  419. // an empty list element should be removed or not.
  420. // node:
  421. // The node to check.
  422. // tags:
  423. // private
  424. if(node.childNodes){
  425. var empty = true;
  426. var i;
  427. for(i = 0; i < node.childNodes.length; i++){
  428. var n = node.childNodes[i];
  429. if(n.nodeType === 1){
  430. if(this._getTagName(n) === "p"){
  431. if(!dojo.trim(n.innerHTML)){
  432. continue;
  433. }
  434. }
  435. empty = false;
  436. break;
  437. }else if(this._isTextElement(n)){
  438. // Check for empty text.
  439. var nv = dojo.trim(n.nodeValue);
  440. if(nv && nv !=="&nbsp;" && nv !== "\u00A0"){
  441. empty = false;
  442. break;
  443. }
  444. }else{
  445. empty = false;
  446. break;
  447. }
  448. }
  449. return empty;
  450. }else{
  451. return true;
  452. }
  453. },
  454. _isInlineFormat: function(tag){
  455. // summary:
  456. // Function to determine if the current tag is an inline
  457. // element that does formatting, as we don't want to
  458. // break/indent around it, as it can screw up text.
  459. // tag:
  460. // The tag to examine
  461. // tags:
  462. // private
  463. switch(tag){
  464. case "a":
  465. case "b":
  466. case "strong":
  467. case "s":
  468. case "strike":
  469. case "i":
  470. case "u":
  471. case "em":
  472. case "sup":
  473. case "sub":
  474. case "span":
  475. case "font":
  476. case "big":
  477. case "cite":
  478. case "q":
  479. case "img":
  480. case "small":
  481. return true;
  482. default:
  483. return false;
  484. }
  485. }
  486. });
  487. // Register this plugin.
  488. dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){
  489. if(o.plugin){ return; }
  490. var name = o.args.name.toLowerCase();
  491. if(name === "blockquote"){
  492. o.plugin = new dojox.editor.plugins.Blockquote({});
  493. }
  494. });
  495. }