]> git.wh0rd.org - tt-rss.git/blame - lib/dijit/_editor/plugins/ViewSource.js.uncompressed.js
make precache_headlines_idle() start slower
[tt-rss.git] / lib / dijit / _editor / plugins / ViewSource.js.uncompressed.js
CommitLineData
1354d172
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/_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
35var 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
563return ViewSource;
564});