source: Dev/trunk/src/client/dijit/_editor/plugins/ViewSource.js

Last change on this file was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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