ViewSource.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  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.ViewSource"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dijit._editor.plugins.ViewSource"] = true;
  8. dojo.provide("dijit._editor.plugins.ViewSource");
  9. dojo.require("dojo.window");
  10. dojo.require("dojo.i18n");
  11. dojo.require("dijit._editor._Plugin");
  12. dojo.require("dijit.form.Button");
  13. dojo.requireLocalization("dijit._editor", "commands", null, "ROOT,ar,az,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");
  14. dojo.declare("dijit._editor.plugins.ViewSource",dijit._editor._Plugin,{
  15. // summary:
  16. // This plugin provides a simple view source capability. When view
  17. // source mode is enabled, it disables all other buttons/plugins on the RTE.
  18. // It also binds to the hotkey: CTRL-SHIFT-F11 for toggling ViewSource mode.
  19. // stripScripts: [public] Boolean
  20. // Boolean flag used to indicate if script tags should be stripped from the document.
  21. // Defaults to true.
  22. stripScripts: true,
  23. // stripComments: [public] Boolean
  24. // Boolean flag used to indicate if comment tags should be stripped from the document.
  25. // Defaults to true.
  26. stripComments: true,
  27. // stripComments: [public] Boolean
  28. // Boolean flag used to indicate if iframe tags should be stripped from the document.
  29. // Defaults to true.
  30. stripIFrames: true,
  31. // readOnly: [const] Boolean
  32. // Boolean flag used to indicate if the source view should be readonly or not.
  33. // Cannot be changed after initialization of the plugin.
  34. // Defaults to false.
  35. readOnly: false,
  36. // _fsPlugin: [private] Object
  37. // Reference to a registered fullscreen plugin so that viewSource knows
  38. // how to scale.
  39. _fsPlugin: null,
  40. toggle: function(){
  41. // summary:
  42. // Function to allow programmatic toggling of the view.
  43. // For Webkit, we have to focus a very particular way.
  44. // when swapping views, otherwise focus doesn't shift right
  45. // but can't focus this way all the time, only for VS changes.
  46. // If we did it all the time, buttons like bold, italic, etc
  47. // break.
  48. if(dojo.isWebKit){this._vsFocused = true;}
  49. this.button.set("checked", !this.button.get("checked"));
  50. },
  51. _initButton: function(){
  52. // summary:
  53. // Over-ride for creation of the resize button.
  54. var strings = dojo.i18n.getLocalization("dijit._editor", "commands"),
  55. editor = this.editor;
  56. this.button = new dijit.form.ToggleButton({
  57. label: strings["viewSource"],
  58. dir: editor.dir,
  59. lang: editor.lang,
  60. showLabel: false,
  61. iconClass: this.iconClassPrefix + " " + this.iconClassPrefix + "ViewSource",
  62. tabIndex: "-1",
  63. onChange: dojo.hitch(this, "_showSource")
  64. });
  65. // IE 7 has a horrible bug with zoom, so we have to create this node
  66. // to cross-check later. Sigh.
  67. if(dojo.isIE == 7){
  68. this._ieFixNode = dojo.create("div", {
  69. style: {
  70. opacity: "0",
  71. zIndex: "-1000",
  72. position: "absolute",
  73. top: "-1000px"
  74. }
  75. }, dojo.body());
  76. }
  77. // Make sure readonly mode doesn't make the wrong cursor appear over the button.
  78. this.button.set("readOnly", false);
  79. },
  80. setEditor: function(/*dijit.Editor*/ editor){
  81. // summary:
  82. // Tell the plugin which Editor it is associated with.
  83. // editor: Object
  84. // The editor object to attach the print capability to.
  85. this.editor = editor;
  86. this._initButton();
  87. this.editor.addKeyHandler(dojo.keys.F12, true, true, dojo.hitch(this, function(e){
  88. // Move the focus before switching
  89. // It'll focus back. Hiding a focused
  90. // node causes issues.
  91. this.button.focus();
  92. this.toggle();
  93. dojo.stopEvent(e);
  94. // Call the focus shift outside of the handler.
  95. setTimeout(dojo.hitch(this, function(){
  96. // We over-ride focus, so we just need to call.
  97. this.editor.focus();
  98. }), 100);
  99. }));
  100. },
  101. _showSource: function(source){
  102. // summary:
  103. // Function to toggle between the source and RTE views.
  104. // source: boolean
  105. // Boolean value indicating if it should be in source mode or not.
  106. // tags:
  107. // private
  108. var ed = this.editor;
  109. var edPlugins = ed._plugins;
  110. var html;
  111. this._sourceShown = source;
  112. var self = this;
  113. try{
  114. if(!this.sourceArea){
  115. this._createSourceView();
  116. }
  117. if(source){
  118. // Update the QueryCommandEnabled function to disable everything but
  119. // the source view mode. Have to over-ride a function, then kick all
  120. // plugins to check their state.
  121. ed._sourceQueryCommandEnabled = ed.queryCommandEnabled;
  122. ed.queryCommandEnabled = function(cmd){
  123. var lcmd = cmd.toLowerCase();
  124. if(lcmd === "viewsource"){
  125. return true;
  126. }else{
  127. return false;
  128. }
  129. };
  130. this.editor.onDisplayChanged();
  131. html = ed.get("value");
  132. html = this._filter(html);
  133. ed.set("value", html);
  134. this._pluginList = [];
  135. dojo.forEach(edPlugins, function(p){
  136. // Turn off any plugins not controlled by queryCommandenabled.
  137. if(!(p instanceof dijit._editor.plugins.ViewSource)){
  138. p.set("disabled", true)
  139. }
  140. });
  141. // We actually do need to trap this plugin and adjust how we
  142. // display the textarea.
  143. if(this._fsPlugin){
  144. this._fsPlugin._getAltViewNode = function(){
  145. return self.sourceArea;
  146. };
  147. }
  148. this.sourceArea.value = html;
  149. var is = dojo._getMarginSize(ed.iframe.parentNode);
  150. dojo.marginBox(this.sourceArea, {
  151. w: is.w,
  152. h: is.h
  153. });
  154. dojo.style(ed.iframe, "display", "none");
  155. dojo.style(this.sourceArea, {
  156. display: "block"
  157. });
  158. var resizer = function(){
  159. // function to handle resize events.
  160. // Will check current VP and only resize if
  161. // different.
  162. var vp = dojo.window.getBox();
  163. if("_prevW" in this && "_prevH" in this){
  164. // No actual size change, ignore.
  165. if(vp.w === this._prevW && vp.h === this._prevH){
  166. return;
  167. }else{
  168. this._prevW = vp.w;
  169. this._prevH = vp.h;
  170. }
  171. }else{
  172. this._prevW = vp.w;
  173. this._prevH = vp.h;
  174. }
  175. if(this._resizer){
  176. clearTimeout(this._resizer);
  177. delete this._resizer;
  178. }
  179. // Timeout it to help avoid spamming resize on IE.
  180. // Works for all browsers.
  181. this._resizer = setTimeout(dojo.hitch(this, function(){
  182. delete this._resizer;
  183. this._resize();
  184. }), 10);
  185. };
  186. this._resizeHandle = dojo.connect(window, "onresize", this, resizer);
  187. //Call this on a delay once to deal with IE glitchiness on initial size.
  188. setTimeout(dojo.hitch(this, this._resize), 100);
  189. //Trigger a check for command enablement/disablement.
  190. this.editor.onNormalizedDisplayChanged();
  191. this.editor.__oldGetValue = this.editor.getValue;
  192. this.editor.getValue = dojo.hitch(this, function() {
  193. var txt = this.sourceArea.value;
  194. txt = this._filter(txt);
  195. return txt;
  196. });
  197. }else{
  198. // First check that we were in source view before doing anything.
  199. // corner case for being called with a value of false and we hadn't
  200. // actually been in source display mode.
  201. if(!ed._sourceQueryCommandEnabled){
  202. return;
  203. }
  204. dojo.disconnect(this._resizeHandle);
  205. delete this._resizeHandle;
  206. if(this.editor.__oldGetValue){
  207. this.editor.getValue = this.editor.__oldGetValue;
  208. delete this.editor.__oldGetValue;
  209. }
  210. // Restore all the plugin buttons state.
  211. ed.queryCommandEnabled = ed._sourceQueryCommandEnabled;
  212. if(!this._readOnly){
  213. html = this.sourceArea.value;
  214. html = this._filter(html);
  215. ed.beginEditing();
  216. ed.set("value", html);
  217. ed.endEditing();
  218. }
  219. dojo.forEach(edPlugins, function(p){
  220. // Turn back on any plugins we turned off.
  221. p.set("disabled", false);
  222. });
  223. dojo.style(this.sourceArea, "display", "none");
  224. dojo.style(ed.iframe, "display", "block");
  225. delete ed._sourceQueryCommandEnabled;
  226. //Trigger a check for command enablement/disablement.
  227. this.editor.onDisplayChanged();
  228. }
  229. // Call a delayed resize to wait for some things to display in header/footer.
  230. setTimeout(dojo.hitch(this, function(){
  231. // Make resize calls.
  232. var parent = ed.domNode.parentNode;
  233. if(parent){
  234. var container = dijit.getEnclosingWidget(parent);
  235. if(container && container.resize){
  236. container.resize();
  237. }
  238. }
  239. ed.resize();
  240. }), 300);
  241. }catch(e){
  242. console.log(e);
  243. }
  244. },
  245. updateState: function(){
  246. // summary:
  247. // Over-ride for button state control for disabled to work.
  248. this.button.set("disabled", this.get("disabled"));
  249. },
  250. _resize: function(){
  251. // summary:
  252. // Internal function to resize the source view
  253. // tags:
  254. // private
  255. var ed = this.editor;
  256. var tbH = ed.getHeaderHeight();
  257. var fH = ed.getFooterHeight();
  258. var eb = dojo.position(ed.domNode);
  259. // Styles are now applied to the internal source container, so we have
  260. // to subtract them off.
  261. var containerPadding = dojo._getPadBorderExtents(ed.iframe.parentNode);
  262. var containerMargin = dojo._getMarginExtents(ed.iframe.parentNode);
  263. var extents = dojo._getPadBorderExtents(ed.domNode);
  264. var mExtents = dojo._getMarginExtents(ed.domNode);
  265. var edb = {
  266. w: eb.w - (extents.w + mExtents.w),
  267. h: eb.h - (tbH + extents.h + mExtents.h + fH)
  268. };
  269. // Fullscreen gets odd, so we need to check for the FS plugin and
  270. // adapt.
  271. if(this._fsPlugin && this._fsPlugin.isFullscreen){
  272. //Okay, probably in FS, adjust.
  273. var vp = dojo.window.getBox();
  274. edb.w = (vp.w - extents.w);
  275. edb.h = (vp.h - (tbH + extents.h + fH));
  276. }
  277. if(dojo.isIE){
  278. // IE is always off by 2px, so we have to adjust here
  279. // Note that IE ZOOM is broken here. I can't get
  280. //it to scale right.
  281. edb.h -= 2;
  282. }
  283. // IE has a horrible zoom bug. So, we have to try and account for
  284. // it and fix up the scaling.
  285. if(this._ieFixNode){
  286. var _ie7zoom = -this._ieFixNode.offsetTop / 1000;
  287. edb.w = Math.floor((edb.w + 0.9) / _ie7zoom);
  288. edb.h = Math.floor((edb.h + 0.9) / _ie7zoom);
  289. }
  290. dojo.marginBox(this.sourceArea, {
  291. w: edb.w - (containerPadding.w + containerMargin.w),
  292. h: edb.h - (containerPadding.h + containerMargin.h)
  293. });
  294. // Scale the parent container too in this case.
  295. dojo.marginBox(ed.iframe.parentNode, {
  296. h: edb.h
  297. });
  298. },
  299. _createSourceView: function(){
  300. // summary:
  301. // Internal function for creating the source view area.
  302. // tags:
  303. // private
  304. var ed = this.editor;
  305. var edPlugins = ed._plugins;
  306. this.sourceArea = dojo.create("textarea");
  307. if(this.readOnly){
  308. dojo.attr(this.sourceArea, "readOnly", true);
  309. this._readOnly = true;
  310. }
  311. dojo.style(this.sourceArea, {
  312. padding: "0px",
  313. margin: "0px",
  314. borderWidth: "0px",
  315. borderStyle: "none"
  316. });
  317. dojo.place(this.sourceArea, ed.iframe, "before");
  318. if(dojo.isIE && ed.iframe.parentNode.lastChild !== ed.iframe){
  319. // There's some weirdo div in IE used for focus control
  320. // But is messed up scaling the textarea if we don't config
  321. // it some so it doesn't have a varying height.
  322. dojo.style(ed.iframe.parentNode.lastChild,{
  323. width: "0px",
  324. height: "0px",
  325. padding: "0px",
  326. margin: "0px",
  327. borderWidth: "0px",
  328. borderStyle: "none"
  329. });
  330. }
  331. // We also need to take over editor focus a bit here, so that focus calls to
  332. // focus the editor will focus to the right node when VS is active.
  333. ed._viewsource_oldFocus = ed.focus;
  334. var self = this;
  335. ed.focus = function(){
  336. if(self._sourceShown){
  337. self.setSourceAreaCaret();
  338. }else{
  339. try{
  340. if(this._vsFocused){
  341. delete this._vsFocused;
  342. // Must focus edit node in this case (webkit only) or
  343. // focus doesn't shift right, but in normal
  344. // cases we focus with the regular function.
  345. dijit.focus(ed.editNode);
  346. }else{
  347. ed._viewsource_oldFocus();
  348. }
  349. }catch(e){
  350. console.log(e);
  351. }
  352. }
  353. };
  354. var i, p;
  355. for(i = 0; i < edPlugins.length; i++){
  356. // We actually do need to trap this plugin and adjust how we
  357. // display the textarea.
  358. p = edPlugins[i];
  359. if(p && (p.declaredClass === "dijit._editor.plugins.FullScreen" ||
  360. p.declaredClass === (dijit._scopeName +
  361. "._editor.plugins.FullScreen"))){
  362. this._fsPlugin = p;
  363. break;
  364. }
  365. }
  366. if(this._fsPlugin){
  367. // Found, we need to over-ride the alt-view node function
  368. // on FullScreen with our own, chain up to parent call when appropriate.
  369. this._fsPlugin._viewsource_getAltViewNode = this._fsPlugin._getAltViewNode;
  370. this._fsPlugin._getAltViewNode = function(){
  371. return self._sourceShown?self.sourceArea:this._viewsource_getAltViewNode();
  372. };
  373. }
  374. // Listen to the source area for key events as well, as we need to be able to hotkey toggle
  375. // it from there too.
  376. this.connect(this.sourceArea, "onkeydown", dojo.hitch(this, function(e){
  377. if(this._sourceShown && e.keyCode == dojo.keys.F12 && e.ctrlKey && e.shiftKey){
  378. this.button.focus();
  379. this.button.set("checked", false);
  380. setTimeout(dojo.hitch(this, function(){ed.focus();}), 100);
  381. dojo.stopEvent(e);
  382. }
  383. }));
  384. },
  385. _stripScripts: function(html){
  386. // summary:
  387. // Strips out script tags from the HTML used in editor.
  388. // html: String
  389. // The HTML to filter
  390. // tags:
  391. // private
  392. if(html){
  393. // Look for closed and unclosed (malformed) script attacks.
  394. html = html.replace(/<\s*script[^>]*>((.|\s)*?)<\\?\/\s*script\s*>/ig, "");
  395. html = html.replace(/<\s*script\b([^<>]|\s)*>?/ig, "");
  396. html = html.replace(/<[^>]*=(\s|)*[("|')]javascript:[^$1][(\s|.)]*[$1][^>]*>/ig, "");
  397. }
  398. return html;
  399. },
  400. _stripComments: function(html){
  401. // summary:
  402. // Strips out comments from the HTML used in editor.
  403. // html: String
  404. // The HTML to filter
  405. // tags:
  406. // private
  407. if(html){
  408. html = html.replace(/<!--(.|\s){1,}?-->/g, "");
  409. }
  410. return html;
  411. },
  412. _stripIFrames: function(html){
  413. // summary:
  414. // Strips out iframe tags from the content, to avoid iframe script
  415. // style injection attacks.
  416. // html: String
  417. // The HTML to filter
  418. // tags:
  419. // private
  420. if(html){
  421. html = html.replace(/<\s*iframe[^>]*>((.|\s)*?)<\\?\/\s*iframe\s*>/ig, "");
  422. }
  423. return html;
  424. },
  425. _filter: function(html){
  426. // summary:
  427. // Internal function to perform some filtering on the HTML.
  428. // html: String
  429. // The HTML to filter
  430. // tags:
  431. // private
  432. if(html){
  433. if(this.stripScripts){
  434. html = this._stripScripts(html);
  435. }
  436. if(this.stripComments){
  437. html = this._stripComments(html);
  438. }
  439. if(this.stripIFrames){
  440. html = this._stripIFrames(html);
  441. }
  442. }
  443. return html;
  444. },
  445. setSourceAreaCaret: function(){
  446. // summary:
  447. // Internal function to set the caret in the sourceArea
  448. // to 0x0
  449. var win = dojo.global;
  450. var elem = this.sourceArea;
  451. dijit.focus(elem);
  452. if(this._sourceShown && !this.readOnly){
  453. if(dojo.isIE){
  454. if(this.sourceArea.createTextRange){
  455. var range = elem.createTextRange();
  456. range.collapse(true);
  457. range.moveStart("character", -99999); // move to 0
  458. range.moveStart("character", 0); // delta from 0 is the correct position
  459. range.moveEnd("character", 0);
  460. range.select();
  461. }
  462. }else if(win.getSelection){
  463. if(elem.setSelectionRange){
  464. elem.setSelectionRange(0,0);
  465. }
  466. }
  467. }
  468. },
  469. destroy: function(){
  470. // summary:
  471. // Over-ride to remove the node used to correct for IE's
  472. // zoom bug.
  473. if(this._ieFixNode){
  474. dojo.body().removeChild(this._ieFixNode);
  475. }
  476. if(this._resizer){
  477. clearTimeout(this._resizer);
  478. delete this._resizer;
  479. }
  480. if(this._resizeHandle){
  481. dojo.disconnect(this._resizeHandle);
  482. delete this._resizeHandle;
  483. }
  484. this.inherited(arguments);
  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 === "viewsource"){
  492. o.plugin = new dijit._editor.plugins.ViewSource({
  493. readOnly: ("readOnly" in o.args)?o.args.readOnly:false,
  494. stripComments: ("stripComments" in o.args)?o.args.stripComments:true,
  495. stripScripts: ("stripScripts" in o.args)?o.args.stripScripts:true,
  496. stripIFrames: ("stripIFrames" in o.args)?o.args.stripIFrames:true
  497. });
  498. }
  499. });
  500. }