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