1 define("dijit/_editor/plugins/ViewSource", [
2 "dojo/_base/array", // array.forEach
3 "dojo/_base/declare", // declare
4 "dojo/dom-attr", // domAttr.set
5 "dojo/dom-construct", // domConstruct.create domConstruct.place
6 "dojo/dom-geometry", // domGeometry.setMarginBox domGeometry.position
7 "dojo/dom-style", // domStyle.set
8 "dojo/_base/event", // event.stop
9 "dojo/i18n", // i18n.getLocalization
10 "dojo/keys", // keys.F12
11 "dojo/_base/lang", // lang.hitch
13 "dojo/_base/sniff", // has("ie") has("webkit")
14 "dojo/_base/window", // win.body win.global
15 "dojo/window", // winUtils.getBox
16 "../../focus", // focus.focus()
18 "../../form/ToggleButton",
19 "../..", // dijit._scopeName
20 "../../registry", // registry.getEnclosingWidget()
21 "dojo/i18n!../nls/commands"
22 ], function(array
, declare
, domAttr
, domConstruct
, domGeometry
, domStyle
, event
, i18n
, keys
, lang
, on
, has
, win
,
23 winUtils
, focus
, _Plugin
, ToggleButton
, dijit
, registry
){
26 var _Plugin = dijit._editor._Plugin;
30 // dijit/_editor/plugins/ViewSource
32 // This plugin provides a simple view source capability.
35 var ViewSource
= declare("dijit._editor.plugins.ViewSource",_Plugin
, {
37 // This plugin provides a simple view source capability. When view
38 // source mode is enabled, it disables all other buttons/plugins on the RTE.
39 // It also binds to the hotkey: CTRL-SHIFT-F11 for toggling ViewSource mode.
41 // stripScripts: [public] Boolean
42 // Boolean flag used to indicate if script tags should be stripped from the document.
46 // stripComments: [public] Boolean
47 // Boolean flag used to indicate if comment tags should be stripped from the document.
51 // stripComments: [public] Boolean
52 // Boolean flag used to indicate if iframe tags should be stripped from the document.
56 // readOnly: [const] Boolean
57 // Boolean flag used to indicate if the source view should be readonly or not.
58 // Cannot be changed after initialization of the plugin.
62 // _fsPlugin: [private] Object
63 // Reference to a registered fullscreen plugin so that viewSource knows
69 // Function to allow programmatic toggling of the view.
71 // For Webkit, we have to focus a very particular way.
72 // when swapping views, otherwise focus doesn't shift right
73 // but can't focus this way all the time, only for VS changes.
74 // If we did it all the time, buttons like bold, italic, etc
76 if(has("webkit")){this._vsFocused
= true;}
77 this.button
.set("checked", !this.button
.get("checked"));
81 _initButton: function(){
83 // Over-ride for creation of the resize button.
84 var strings
= i18n
.getLocalization("dijit._editor", "commands"),
86 this.button
= new ToggleButton({
87 label
: strings
["viewSource"],
91 iconClass
: this.iconClassPrefix
+ " " + this.iconClassPrefix
+ "ViewSource",
93 onChange
: lang
.hitch(this, "_showSource")
96 // IE 7 has a horrible bug with zoom, so we have to create this node
97 // to cross-check later. Sigh.
99 this._ieFixNode
= domConstruct
.create("div", {
103 position
: "absolute",
108 // Make sure readonly mode doesn't make the wrong cursor appear over the button.
109 this.button
.set("readOnly", false);
113 setEditor: function(/*dijit.Editor*/ editor
){
115 // Tell the plugin which Editor it is associated with.
117 // The editor object to attach the print capability to.
118 this.editor
= editor
;
121 this.editor
.addKeyHandler(keys
.F12
, true, true, lang
.hitch(this, function(e
){
122 // Move the focus before switching
123 // It'll focus back. Hiding a focused
124 // node causes issues.
129 // Call the focus shift outside of the handler.
130 setTimeout(lang
.hitch(this, function(){
131 // We over-ride focus, so we just need to call.
137 _showSource: function(source
){
139 // Function to toggle between the source and RTE views.
141 // Boolean value indicating if it should be in source mode or not.
144 var ed
= this.editor
;
145 var edPlugins
= ed
._plugins
;
147 this._sourceShown
= source
;
150 if(!this.sourceArea
){
151 this._createSourceView();
154 // Update the QueryCommandEnabled function to disable everything but
155 // the source view mode. Have to over-ride a function, then kick all
156 // plugins to check their state.
157 ed
._sourceQueryCommandEnabled
= ed
.queryCommandEnabled
;
158 ed
.queryCommandEnabled = function(cmd
){
159 return cmd
.toLowerCase() === "viewsource";
161 this.editor
.onDisplayChanged();
162 html
= ed
.get("value");
163 html
= this._filter(html
);
164 ed
.set("value", html
);
165 array
.forEach(edPlugins
, function(p
){
166 // Turn off any plugins not controlled by queryCommandenabled.
167 if(!(p
instanceof ViewSource
)){
168 p
.set("disabled", true)
172 // We actually do need to trap this plugin and adjust how we
173 // display the textarea.
175 this._fsPlugin
._getAltViewNode = function(){
176 return self
.sourceArea
;
180 this.sourceArea
.value
= html
;
182 // Since neither iframe nor textarea have margin, border, or padding,
183 // just set sizes equal
184 this.sourceArea
.style
.height
= ed
.iframe
.style
.height
;
185 this.sourceArea
.style
.width
= ed
.iframe
.style
.width
;
186 domStyle
.set(ed
.iframe
, "display", "none");
187 domStyle
.set(this.sourceArea
, {
191 var resizer = function(){
192 // function to handle resize events.
193 // Will check current VP and only resize if
195 var vp
= winUtils
.getBox();
197 if("_prevW" in this && "_prevH" in this){
198 // No actual size change, ignore.
199 if(vp
.w
=== this._prevW
&& vp
.h
=== this._prevH
){
210 clearTimeout(this._resizer
);
211 delete this._resizer
;
213 // Timeout it to help avoid spamming resize on IE.
214 // Works for all browsers.
215 this._resizer
= setTimeout(lang
.hitch(this, function(){
216 delete this._resizer
;
220 this._resizeHandle
= on(window
, "resize", lang
.hitch(this, resizer
));
222 //Call this on a delay once to deal with IE glitchiness on initial size.
223 setTimeout(lang
.hitch(this, this._resize
), 100);
225 //Trigger a check for command enablement/disablement.
226 this.editor
.onNormalizedDisplayChanged();
228 this.editor
.__oldGetValue
= this.editor
.getValue
;
229 this.editor
.getValue
= lang
.hitch(this, function(){
230 var txt
= this.sourceArea
.value
;
231 txt
= this._filter(txt
);
235 // First check that we were in source view before doing anything.
236 // corner case for being called with a value of false and we hadn't
237 // actually been in source display mode.
238 if(!ed
._sourceQueryCommandEnabled
){
241 this._resizeHandle
.remove();
242 delete this._resizeHandle
;
244 if(this.editor
.__oldGetValue
){
245 this.editor
.getValue
= this.editor
.__oldGetValue
;
246 delete this.editor
.__oldGetValue
;
249 // Restore all the plugin buttons state.
250 ed
.queryCommandEnabled
= ed
._sourceQueryCommandEnabled
;
252 html
= this.sourceArea
.value
;
253 html
= this._filter(html
);
255 ed
.set("value", html
);
259 array
.forEach(edPlugins
, function(p
){
260 // Turn back on any plugins we turned off.
261 p
.set("disabled", false);
264 domStyle
.set(this.sourceArea
, "display", "none");
265 domStyle
.set(ed
.iframe
, "display", "block");
266 delete ed
._sourceQueryCommandEnabled
;
268 //Trigger a check for command enablement/disablement.
269 this.editor
.onDisplayChanged();
271 // Call a delayed resize to wait for some things to display in header/footer.
272 setTimeout(lang
.hitch(this, function(){
273 // Make resize calls.
274 var parent
= ed
.domNode
.parentNode
;
276 var container
= registry
.getEnclosingWidget(parent
);
277 if(container
&& container
.resize
){
288 updateState: function(){
290 // Over-ride for button state control for disabled to work.
291 this.button
.set("disabled", this.get("disabled"));
296 // Internal function to resize the source view
299 var ed
= this.editor
;
300 var tbH
= ed
.getHeaderHeight();
301 var fH
= ed
.getFooterHeight();
302 var eb
= domGeometry
.position(ed
.domNode
);
304 // Styles are now applied to the internal source container, so we have
305 // to subtract them off.
306 var containerPadding
= domGeometry
.getPadBorderExtents(ed
.iframe
.parentNode
);
307 var containerMargin
= domGeometry
.getMarginExtents(ed
.iframe
.parentNode
);
309 var extents
= domGeometry
.getPadBorderExtents(ed
.domNode
);
312 h
: eb
.h
- (tbH
+ extents
.h
+ + fH
)
315 // Fullscreen gets odd, so we need to check for the FS plugin and
317 if(this._fsPlugin
&& this._fsPlugin
.isFullscreen
){
318 //Okay, probably in FS, adjust.
319 var vp
= winUtils
.getBox();
320 edb
.w
= (vp
.w
- extents
.w
);
321 edb
.h
= (vp
.h
- (tbH
+ extents
.h
+ fH
));
325 // IE is always off by 2px, so we have to adjust here
326 // Note that IE ZOOM is broken here. I can't get
331 // IE has a horrible zoom bug. So, we have to try and account for
332 // it and fix up the scaling.
334 var _ie7zoom
= -this._ieFixNode
.offsetTop
/ 1000;
335 edb
.w
= Math
.floor((edb
.w
+ 0.9) / _ie7zoom
);
336 edb
.h
= Math
.floor((edb
.h
+ 0.9) / _ie7zoom
);
339 domGeometry
.setMarginBox(this.sourceArea
, {
340 w
: edb
.w
- (containerPadding
.w
+ containerMargin
.w
),
341 h
: edb
.h
- (containerPadding
.h
+ containerMargin
.h
)
344 // Scale the parent container too in this case.
345 domGeometry
.setMarginBox(ed
.iframe
.parentNode
, {
350 _createSourceView: function(){
352 // Internal function for creating the source view area.
355 var ed
= this.editor
;
356 var edPlugins
= ed
._plugins
;
357 this.sourceArea
= domConstruct
.create("textarea");
359 domAttr
.set(this.sourceArea
, "readOnly", true);
360 this._readOnly
= true;
362 domStyle
.set(this.sourceArea
, {
368 domConstruct
.place(this.sourceArea
, ed
.iframe
, "before");
370 if(has("ie") && ed
.iframe
.parentNode
.lastChild
!== ed
.iframe
){
371 // There's some weirdo div in IE used for focus control
372 // But is messed up scaling the textarea if we don't config
373 // it some so it doesn't have a varying height.
374 domStyle
.set(ed
.iframe
.parentNode
.lastChild
,{
384 // We also need to take over editor focus a bit here, so that focus calls to
385 // focus the editor will focus to the right node when VS is active.
386 ed
._viewsource_oldFocus
= ed
.focus
;
388 ed
.focus = function(){
389 if(self
._sourceShown
){
390 self
.setSourceAreaCaret();
394 delete this._vsFocused
;
395 // Must focus edit node in this case (webkit only) or
396 // focus doesn't shift right, but in normal
397 // cases we focus with the regular function.
398 focus
.focus(ed
.editNode
);
400 ed
._viewsource_oldFocus();
409 for(i
= 0; i
< edPlugins
.length
; i
++){
410 // We actually do need to trap this plugin and adjust how we
411 // display the textarea.
413 if(p
&& (p
.declaredClass
=== "dijit._editor.plugins.FullScreen" ||
414 p
.declaredClass
=== (dijit
._scopeName
+
415 "._editor.plugins.FullScreen"))){
421 // Found, we need to over-ride the alt-view node function
422 // on FullScreen with our own, chain up to parent call when appropriate.
423 this._fsPlugin
._viewsource_getAltViewNode
= this._fsPlugin
._getAltViewNode
;
424 this._fsPlugin
._getAltViewNode = function(){
425 return self
._sourceShown
?self
.sourceArea
:this._viewsource_getAltViewNode();
429 // Listen to the source area for key events as well, as we need to be able to hotkey toggle
430 // it from there too.
431 this.connect(this.sourceArea
, "onkeydown", lang
.hitch(this, function(e
){
432 if(this._sourceShown
&& e
.keyCode
== keys
.F12
&& e
.ctrlKey
&& e
.shiftKey
){
434 this.button
.set("checked", false);
435 setTimeout(lang
.hitch(this, function(){ed
.focus();}), 100);
441 _stripScripts: function(html
){
443 // Strips out script tags from the HTML used in editor.
445 // The HTML to filter
449 // Look for closed and unclosed (malformed) script attacks.
450 html
= html
.replace(/<\s*script[^>]*>((.|\s)*?)<\\?\/\s*script\s*>/ig, "");
451 html
= html
.replace(/<\s*script\b([^<>]|\s)*>?/ig, "");
452 html
= html
.replace(/<[^>]*=(\s|)*[("|')]javascript:[^$1][(\s|.)]*[$1][^>]*>/ig, "");
457 _stripComments: function(html
){
459 // Strips out comments from the HTML used in editor.
461 // The HTML to filter
465 html
= html
.replace(/<!--(.|\s){1,}?-->/g, "");
470 _stripIFrames: function(html
){
472 // Strips out iframe tags from the content, to avoid iframe script
473 // style injection attacks.
475 // The HTML to filter
479 html
= html
.replace(/<\s*iframe[^>]*>((.|\s)*?)<\\?\/\s*iframe\s*>/ig, "");
484 _filter: function(html
){
486 // Internal function to perform some filtering on the HTML.
488 // The HTML to filter
492 if(this.stripScripts
){
493 html
= this._stripScripts(html
);
495 if(this.stripComments
){
496 html
= this._stripComments(html
);
498 if(this.stripIFrames
){
499 html
= this._stripIFrames(html
);
505 setSourceAreaCaret: function(){
507 // Internal function to set the caret in the sourceArea
509 var global
= win
.global
;
510 var elem
= this.sourceArea
;
512 if(this._sourceShown
&& !this.readOnly
){
514 if(this.sourceArea
.createTextRange
){
515 var range
= elem
.createTextRange();
516 range
.collapse(true);
517 range
.moveStart("character", -99999); // move to 0
518 range
.moveStart("character", 0); // delta from 0 is the correct position
519 range
.moveEnd("character", 0);
522 }else if(global
.getSelection
){
523 if(elem
.setSelectionRange
){
524 elem
.setSelectionRange(0,0);
532 // Over-ride to remove the node used to correct for IE's
535 win
.body().removeChild(this._ieFixNode
);
538 clearTimeout(this._resizer
);
539 delete this._resizer
;
541 if(this._resizeHandle
){
542 this._resizeHandle
.remove();
543 delete this._resizeHandle
;
545 this.inherited(arguments
);
549 // Register this plugin.
550 // For back-compat accept "viewsource" (all lowercase) too, remove in 2.0
551 _Plugin
.registry
["viewSource"] = _Plugin
.registry
["viewsource"] = function(args
){
552 return new ViewSource({
553 readOnly
: ("readOnly" in args
)?args
.readOnly
:false,
554 stripComments
: ("stripComments" in args
)?args
.stripComments
:true,
555 stripScripts
: ("stripScripts" in args
)?args
.stripScripts
:true,
556 stripIFrames
: ("stripIFrames" in args
)?args
.stripIFrames
:true