]>
Commit | Line | Data |
---|---|---|
1354d172 AD |
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 | |
12 | "dojo/on", // on() | |
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() | |
17 | "../_Plugin", | |
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){ | |
24 | ||
25 | /*===== | |
26 | var _Plugin = dijit._editor._Plugin; | |
27 | =====*/ | |
28 | ||
29 | // module: | |
30 | // dijit/_editor/plugins/ViewSource | |
31 | // summary: | |
32 | // This plugin provides a simple view source capability. | |
33 | ||
34 | ||
35 | var ViewSource = declare("dijit._editor.plugins.ViewSource",_Plugin, { | |
36 | // summary: | |
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. | |
40 | ||
41 | // stripScripts: [public] Boolean | |
42 | // Boolean flag used to indicate if script tags should be stripped from the document. | |
43 | // Defaults to true. | |
44 | stripScripts: true, | |
45 | ||
46 | // stripComments: [public] Boolean | |
47 | // Boolean flag used to indicate if comment tags should be stripped from the document. | |
48 | // Defaults to true. | |
49 | stripComments: true, | |
50 | ||
51 | // stripComments: [public] Boolean | |
52 | // Boolean flag used to indicate if iframe tags should be stripped from the document. | |
53 | // Defaults to true. | |
54 | stripIFrames: true, | |
55 | ||
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. | |
59 | // Defaults to false. | |
60 | readOnly: false, | |
61 | ||
62 | // _fsPlugin: [private] Object | |
63 | // Reference to a registered fullscreen plugin so that viewSource knows | |
64 | // how to scale. | |
65 | _fsPlugin: null, | |
66 | ||
67 | toggle: function(){ | |
68 | // summary: | |
69 | // Function to allow programmatic toggling of the view. | |
70 | ||
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 | |
75 | // break. | |
76 | if(has("webkit")){this._vsFocused = true;} | |
77 | this.button.set("checked", !this.button.get("checked")); | |
78 | ||
79 | }, | |
80 | ||
81 | _initButton: function(){ | |
82 | // summary: | |
83 | // Over-ride for creation of the resize button. | |
84 | var strings = i18n.getLocalization("dijit._editor", "commands"), | |
85 | editor = this.editor; | |
86 | this.button = new ToggleButton({ | |
87 | label: strings["viewSource"], | |
88 | dir: editor.dir, | |
89 | lang: editor.lang, | |
90 | showLabel: false, | |
91 | iconClass: this.iconClassPrefix + " " + this.iconClassPrefix + "ViewSource", | |
92 | tabIndex: "-1", | |
93 | onChange: lang.hitch(this, "_showSource") | |
94 | }); | |
95 | ||
96 | // IE 7 has a horrible bug with zoom, so we have to create this node | |
97 | // to cross-check later. Sigh. | |
98 | if(has("ie") == 7){ | |
99 | this._ieFixNode = domConstruct.create("div", { | |
100 | style: { | |
101 | opacity: "0", | |
102 | zIndex: "-1000", | |
103 | position: "absolute", | |
104 | top: "-1000px" | |
105 | } | |
106 | }, win.body()); | |
107 | } | |
108 | // Make sure readonly mode doesn't make the wrong cursor appear over the button. | |
109 | this.button.set("readOnly", false); | |
110 | }, | |
111 | ||
112 | ||
113 | setEditor: function(/*dijit.Editor*/ editor){ | |
114 | // summary: | |
115 | // Tell the plugin which Editor it is associated with. | |
116 | // editor: Object | |
117 | // The editor object to attach the print capability to. | |
118 | this.editor = editor; | |
119 | this._initButton(); | |
120 | ||
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. | |
125 | this.button.focus(); | |
126 | this.toggle(); | |
127 | event.stop(e); | |
128 | ||
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. | |
132 | this.editor.focus(); | |
133 | }), 100); | |
134 | })); | |
135 | }, | |
136 | ||
137 | _showSource: function(source){ | |
138 | // summary: | |
139 | // Function to toggle between the source and RTE views. | |
140 | // source: boolean | |
141 | // Boolean value indicating if it should be in source mode or not. | |
142 | // tags: | |
143 | // private | |
144 | var ed = this.editor; | |
145 | var edPlugins = ed._plugins; | |
146 | var html; | |
147 | this._sourceShown = source; | |
148 | var self = this; | |
149 | try{ | |
150 | if(!this.sourceArea){ | |
151 | this._createSourceView(); | |
152 | } | |
153 | if(source){ | |
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"; | |
160 | }; | |
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) | |
169 | } | |
170 | }); | |
171 | ||
172 | // We actually do need to trap this plugin and adjust how we | |
173 | // display the textarea. | |
174 | if(this._fsPlugin){ | |
175 | this._fsPlugin._getAltViewNode = function(){ | |
176 | return self.sourceArea; | |
177 | }; | |
178 | } | |
179 | ||
180 | this.sourceArea.value = html; | |
181 | ||
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, { | |
188 | display: "block" | |
189 | }); | |
190 | ||
191 | var resizer = function(){ | |
192 | // function to handle resize events. | |
193 | // Will check current VP and only resize if | |
194 | // different. | |
195 | var vp = winUtils.getBox(); | |
196 | ||
197 | if("_prevW" in this && "_prevH" in this){ | |
198 | // No actual size change, ignore. | |
199 | if(vp.w === this._prevW && vp.h === this._prevH){ | |
200 | return; | |
201 | }else{ | |
202 | this._prevW = vp.w; | |
203 | this._prevH = vp.h; | |
204 | } | |
205 | }else{ | |
206 | this._prevW = vp.w; | |
207 | this._prevH = vp.h; | |
208 | } | |
209 | if(this._resizer){ | |
210 | clearTimeout(this._resizer); | |
211 | delete this._resizer; | |
212 | } | |
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; | |
217 | this._resize(); | |
218 | }), 10); | |
219 | }; | |
220 | this._resizeHandle = on(window, "resize", lang.hitch(this, resizer)); | |
221 | ||
222 | //Call this on a delay once to deal with IE glitchiness on initial size. | |
223 | setTimeout(lang.hitch(this, this._resize), 100); | |
224 | ||
225 | //Trigger a check for command enablement/disablement. | |
226 | this.editor.onNormalizedDisplayChanged(); | |
227 | ||
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); | |
232 | return txt; | |
233 | }); | |
234 | }else{ | |
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){ | |
239 | return; | |
240 | } | |
241 | this._resizeHandle.remove(); | |
242 | delete this._resizeHandle; | |
243 | ||
244 | if(this.editor.__oldGetValue){ | |
245 | this.editor.getValue = this.editor.__oldGetValue; | |
246 | delete this.editor.__oldGetValue; | |
247 | } | |
248 | ||
249 | // Restore all the plugin buttons state. | |
250 | ed.queryCommandEnabled = ed._sourceQueryCommandEnabled; | |
251 | if(!this._readOnly){ | |
252 | html = this.sourceArea.value; | |
253 | html = this._filter(html); | |
254 | ed.beginEditing(); | |
255 | ed.set("value", html); | |
256 | ed.endEditing(); | |
257 | } | |
258 | ||
259 | array.forEach(edPlugins, function(p){ | |
260 | // Turn back on any plugins we turned off. | |
261 | p.set("disabled", false); | |
262 | }); | |
263 | ||
264 | domStyle.set(this.sourceArea, "display", "none"); | |
265 | domStyle.set(ed.iframe, "display", "block"); | |
266 | delete ed._sourceQueryCommandEnabled; | |
267 | ||
268 | //Trigger a check for command enablement/disablement. | |
269 | this.editor.onDisplayChanged(); | |
270 | } | |
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; | |
275 | if(parent){ | |
276 | var container = registry.getEnclosingWidget(parent); | |
277 | if(container && container.resize){ | |
278 | container.resize(); | |
279 | } | |
280 | } | |
281 | ed.resize(); | |
282 | }), 300); | |
283 | }catch(e){ | |
284 | console.log(e); | |
285 | } | |
286 | }, | |
287 | ||
288 | updateState: function(){ | |
289 | // summary: | |
290 | // Over-ride for button state control for disabled to work. | |
291 | this.button.set("disabled", this.get("disabled")); | |
292 | }, | |
293 | ||
294 | _resize: function(){ | |
295 | // summary: | |
296 | // Internal function to resize the source view | |
297 | // tags: | |
298 | // private | |
299 | var ed = this.editor; | |
300 | var tbH = ed.getHeaderHeight(); | |
301 | var fH = ed.getFooterHeight(); | |
302 | var eb = domGeometry.position(ed.domNode); | |
303 | ||
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); | |
308 | ||
309 | var extents = domGeometry.getPadBorderExtents(ed.domNode); | |
310 | var edb = { | |
311 | w: eb.w - extents.w, | |
312 | h: eb.h - (tbH + extents.h + + fH) | |
313 | }; | |
314 | ||
315 | // Fullscreen gets odd, so we need to check for the FS plugin and | |
316 | // adapt. | |
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)); | |
322 | } | |
323 | ||
324 | if(has("ie")){ | |
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 | |
327 | //it to scale right. | |
328 | edb.h -= 2; | |
329 | } | |
330 | ||
331 | // IE has a horrible zoom bug. So, we have to try and account for | |
332 | // it and fix up the scaling. | |
333 | if(this._ieFixNode){ | |
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); | |
337 | } | |
338 | ||
339 | domGeometry.setMarginBox(this.sourceArea, { | |
340 | w: edb.w - (containerPadding.w + containerMargin.w), | |
341 | h: edb.h - (containerPadding.h + containerMargin.h) | |
342 | }); | |
343 | ||
344 | // Scale the parent container too in this case. | |
345 | domGeometry.setMarginBox(ed.iframe.parentNode, { | |
346 | h: edb.h | |
347 | }); | |
348 | }, | |
349 | ||
350 | _createSourceView: function(){ | |
351 | // summary: | |
352 | // Internal function for creating the source view area. | |
353 | // tags: | |
354 | // private | |
355 | var ed = this.editor; | |
356 | var edPlugins = ed._plugins; | |
357 | this.sourceArea = domConstruct.create("textarea"); | |
358 | if(this.readOnly){ | |
359 | domAttr.set(this.sourceArea, "readOnly", true); | |
360 | this._readOnly = true; | |
361 | } | |
362 | domStyle.set(this.sourceArea, { | |
363 | padding: "0px", | |
364 | margin: "0px", | |
365 | borderWidth: "0px", | |
366 | borderStyle: "none" | |
367 | }); | |
368 | domConstruct.place(this.sourceArea, ed.iframe, "before"); | |
369 | ||
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,{ | |
375 | width: "0px", | |
376 | height: "0px", | |
377 | padding: "0px", | |
378 | margin: "0px", | |
379 | borderWidth: "0px", | |
380 | borderStyle: "none" | |
381 | }); | |
382 | } | |
383 | ||
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; | |
387 | var self = this; | |
388 | ed.focus = function(){ | |
389 | if(self._sourceShown){ | |
390 | self.setSourceAreaCaret(); | |
391 | }else{ | |
392 | try{ | |
393 | if(this._vsFocused){ | |
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); | |
399 | }else{ | |
400 | ed._viewsource_oldFocus(); | |
401 | } | |
402 | }catch(e){ | |
403 | console.log(e); | |
404 | } | |
405 | } | |
406 | }; | |
407 | ||
408 | var i, p; | |
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. | |
412 | p = edPlugins[i]; | |
413 | if(p && (p.declaredClass === "dijit._editor.plugins.FullScreen" || | |
414 | p.declaredClass === (dijit._scopeName + | |
415 | "._editor.plugins.FullScreen"))){ | |
416 | this._fsPlugin = p; | |
417 | break; | |
418 | } | |
419 | } | |
420 | if(this._fsPlugin){ | |
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(); | |
426 | }; | |
427 | } | |
428 | ||
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){ | |
433 | this.button.focus(); | |
434 | this.button.set("checked", false); | |
435 | setTimeout(lang.hitch(this, function(){ed.focus();}), 100); | |
436 | event.stop(e); | |
437 | } | |
438 | })); | |
439 | }, | |
440 | ||
441 | _stripScripts: function(html){ | |
442 | // summary: | |
443 | // Strips out script tags from the HTML used in editor. | |
444 | // html: String | |
445 | // The HTML to filter | |
446 | // tags: | |
447 | // private | |
448 | if(html){ | |
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, ""); | |
453 | } | |
454 | return html; | |
455 | }, | |
456 | ||
457 | _stripComments: function(html){ | |
458 | // summary: | |
459 | // Strips out comments from the HTML used in editor. | |
460 | // html: String | |
461 | // The HTML to filter | |
462 | // tags: | |
463 | // private | |
464 | if(html){ | |
465 | html = html.replace(/<!--(.|\s){1,}?-->/g, ""); | |
466 | } | |
467 | return html; | |
468 | }, | |
469 | ||
470 | _stripIFrames: function(html){ | |
471 | // summary: | |
472 | // Strips out iframe tags from the content, to avoid iframe script | |
473 | // style injection attacks. | |
474 | // html: String | |
475 | // The HTML to filter | |
476 | // tags: | |
477 | // private | |
478 | if(html){ | |
479 | html = html.replace(/<\s*iframe[^>]*>((.|\s)*?)<\\?\/\s*iframe\s*>/ig, ""); | |
480 | } | |
481 | return html; | |
482 | }, | |
483 | ||
484 | _filter: function(html){ | |
485 | // summary: | |
486 | // Internal function to perform some filtering on the HTML. | |
487 | // html: String | |
488 | // The HTML to filter | |
489 | // tags: | |
490 | // private | |
491 | if(html){ | |
492 | if(this.stripScripts){ | |
493 | html = this._stripScripts(html); | |
494 | } | |
495 | if(this.stripComments){ | |
496 | html = this._stripComments(html); | |
497 | } | |
498 | if(this.stripIFrames){ | |
499 | html = this._stripIFrames(html); | |
500 | } | |
501 | } | |
502 | return html; | |
503 | }, | |
504 | ||
505 | setSourceAreaCaret: function(){ | |
506 | // summary: | |
507 | // Internal function to set the caret in the sourceArea | |
508 | // to 0x0 | |
509 | var global = win.global; | |
510 | var elem = this.sourceArea; | |
511 | focus.focus(elem); | |
512 | if(this._sourceShown && !this.readOnly){ | |
513 | if(has("ie")){ | |
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); | |
520 | range.select(); | |
521 | } | |
522 | }else if(global.getSelection){ | |
523 | if(elem.setSelectionRange){ | |
524 | elem.setSelectionRange(0,0); | |
525 | } | |
526 | } | |
527 | } | |
528 | }, | |
529 | ||
530 | destroy: function(){ | |
531 | // summary: | |
532 | // Over-ride to remove the node used to correct for IE's | |
533 | // zoom bug. | |
534 | if(this._ieFixNode){ | |
535 | win.body().removeChild(this._ieFixNode); | |
536 | } | |
537 | if(this._resizer){ | |
538 | clearTimeout(this._resizer); | |
539 | delete this._resizer; | |
540 | } | |
541 | if(this._resizeHandle){ | |
542 | this._resizeHandle.remove(); | |
543 | delete this._resizeHandle; | |
544 | } | |
545 | this.inherited(arguments); | |
546 | } | |
547 | }); | |
548 | ||
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 | |
557 | }); | |
558 | }; | |
559 | ||
560 | ||
561 | ||
562 | ||
563 | return ViewSource; | |
564 | }); |