Fix #6640 - Inspection Schedule (partial fix)
[roojs1] / Roo / HtmlEditorCore.js
1 //<script type="text/javascript">
2
3 /*
4  * Based  Ext JS Library 1.1.1
5  * Copyright(c) 2006-2007, Ext JS, LLC.
6  * LGPL
7  *
8  */
9  
10 /**
11  * @class Roo.HtmlEditorCore
12  * @extends Roo.Component
13  * Provides a the editing component for the HTML editors in Roo. (bootstrap and Roo.form)
14  *
15  * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
16  */
17
18 Roo.HtmlEditorCore = function(config){
19     
20     
21     Roo.HtmlEditorCore.superclass.constructor.call(this, config);
22     
23     
24     this.addEvents({
25         /**
26          * @event initialize
27          * Fires when the editor is fully initialized (including the iframe)
28          * @param {Roo.HtmlEditorCore} this
29          */
30         initialize: true,
31         /**
32          * @event activate
33          * Fires when the editor is first receives the focus. Any insertion must wait
34          * until after this event.
35          * @param {Roo.HtmlEditorCore} this
36          */
37         activate: true,
38          /**
39          * @event beforesync
40          * Fires before the textarea is updated with content from the editor iframe. Return false
41          * to cancel the sync.
42          * @param {Roo.HtmlEditorCore} this
43          * @param {String} html
44          */
45         beforesync: true,
46          /**
47          * @event beforepush
48          * Fires before the iframe editor is updated with content from the textarea. Return false
49          * to cancel the push.
50          * @param {Roo.HtmlEditorCore} this
51          * @param {String} html
52          */
53         beforepush: true,
54          /**
55          * @event sync
56          * Fires when the textarea is updated with content from the editor iframe.
57          * @param {Roo.HtmlEditorCore} this
58          * @param {String} html
59          */
60         sync: true,
61          /**
62          * @event push
63          * Fires when the iframe editor is updated with content from the textarea.
64          * @param {Roo.HtmlEditorCore} this
65          * @param {String} html
66          */
67         push: true,
68         
69         /**
70          * @event editorevent
71          * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
72          * @param {Roo.HtmlEditorCore} this
73          */
74         editorevent: true
75         
76     });
77     
78     // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
79     
80     // defaults : white / black...
81     this.applyBlacklists();
82     
83     
84     
85 };
86
87
88 Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
89
90
91      /**
92      * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field 
93      */
94     
95     owner : false,
96     
97      /**
98      * @cfg {String} resizable  's' or 'se' or 'e' - wrapps the element in a
99      *                        Roo.resizable.
100      */
101     resizable : false,
102      /**
103      * @cfg {Number} height (in pixels)
104      */   
105     height: 300,
106    /**
107      * @cfg {Number} width (in pixels)
108      */   
109     width: 500,
110     
111     /**
112      * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
113      * 
114      */
115     stylesheets: false,
116     
117     // id of frame..
118     frameId: false,
119     
120     // private properties
121     validationEvent : false,
122     deferHeight: true,
123     initialized : false,
124     activated : false,
125     sourceEditMode : false,
126     onFocus : Roo.emptyFn,
127     iframePad:3,
128     hideMode:'offsets',
129     
130     clearUp: true,
131     
132     // blacklist + whitelisted elements..
133     black: false,
134     white: false,
135      
136     bodyCls : '',
137
138     /**
139      * Protected method that will not generally be called directly. It
140      * is called when the editor initializes the iframe with HTML contents. Override this method if you
141      * want to change the initialization markup of the iframe (e.g. to add stylesheets).
142      */
143     getDocMarkup : function(){
144         // body styles..
145         var st = '';
146         
147         // inherit styels from page...?? 
148         if (this.stylesheets === false) {
149             
150             Roo.get(document.head).select('style').each(function(node) {
151                 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
152             });
153             
154             Roo.get(document.head).select('link').each(function(node) { 
155                 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
156             });
157             
158         } else if (!this.stylesheets.length) {
159                 // simple..
160                 st = '<style type="text/css">' +
161                     'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
162                    '</style>';
163         } else {
164             for (var i in this.stylesheets) { 
165                 st += '<link rel="stylesheet" href="' + this.stylesheets[i] +'" type="text/css">';
166             }
167             
168         }
169         
170         st +=  '<style type="text/css">' +
171             'IMG { cursor: pointer } ' +
172         '</style>';
173
174         var cls = 'roo-htmleditor-body';
175         
176         if(this.bodyCls.length){
177             cls += ' ' + this.bodyCls;
178         }
179         
180         return '<html><head>' + st  +
181             //<style type="text/css">' +
182             //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
183             //'</style>' +
184             ' </head><body contenteditable="true" data-enable-grammerly="true" class="' +  cls + '"></body></html>';
185     },
186
187     // private
188     onRender : function(ct, position)
189     {
190         var _t = this;
191         //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
192         this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
193         
194         
195         this.el.dom.style.border = '0 none';
196         this.el.dom.setAttribute('tabIndex', -1);
197         this.el.addClass('x-hidden hide');
198         
199         
200         
201         if(Roo.isIE){ // fix IE 1px bogus margin
202             this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
203         }
204        
205         
206         this.frameId = Roo.id();
207         
208          
209         
210         var iframe = this.owner.wrap.createChild({
211             tag: 'iframe',
212             cls: 'form-control', // bootstrap..
213             id: this.frameId,
214             name: this.frameId,
215             frameBorder : 'no',
216             'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL  :  "javascript:false"
217         }, this.el
218         );
219         
220         
221         this.iframe = iframe.dom;
222
223          this.assignDocWin();
224         
225         this.doc.designMode = 'on';
226        
227         this.doc.open();
228         this.doc.write(this.getDocMarkup());
229         this.doc.close();
230
231         
232         var task = { // must defer to wait for browser to be ready
233             run : function(){
234                 //console.log("run task?" + this.doc.readyState);
235                 this.assignDocWin();
236                 if(this.doc.body || this.doc.readyState == 'complete'){
237                     try {
238                         this.doc.designMode="on";
239                     } catch (e) {
240                         return;
241                     }
242                     Roo.TaskMgr.stop(task);
243                     this.initEditor.defer(10, this);
244                 }
245             },
246             interval : 10,
247             duration: 10000,
248             scope: this
249         };
250         Roo.TaskMgr.start(task);
251
252     },
253
254     // private
255     onResize : function(w, h)
256     {
257          Roo.log('resize: ' +w + ',' + h );
258         //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
259         if(!this.iframe){
260             return;
261         }
262         if(typeof w == 'number'){
263             
264             this.iframe.style.width = w + 'px';
265         }
266         if(typeof h == 'number'){
267             
268             this.iframe.style.height = h + 'px';
269             if(this.doc){
270                 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
271             }
272         }
273         
274     },
275
276     /**
277      * Toggles the editor between standard and source edit mode.
278      * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
279      */
280     toggleSourceEdit : function(sourceEditMode){
281         
282         this.sourceEditMode = sourceEditMode === true;
283         
284         if(this.sourceEditMode){
285  
286             Roo.get(this.iframe).addClass(['x-hidden','hide']);     //FIXME - what's the BS styles for these
287             
288         }else{
289             Roo.get(this.iframe).removeClass(['x-hidden','hide']);
290             //this.iframe.className = '';
291             this.deferFocus();
292         }
293         //this.setSize(this.owner.wrap.getSize());
294         //this.fireEvent('editmodechange', this, this.sourceEditMode);
295     },
296
297     
298   
299
300     /**
301      * Protected method that will not generally be called directly. If you need/want
302      * custom HTML cleanup, this is the method you should override.
303      * @param {String} html The HTML to be cleaned
304      * return {String} The cleaned HTML
305      */
306     cleanHtml : function(html){
307         html = String(html);
308         if(html.length > 5){
309             if(Roo.isSafari){ // strip safari nonsense
310                 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
311             }
312         }
313         if(html == '&nbsp;'){
314             html = '';
315         }
316         return html;
317     },
318
319     /**
320      * HTML Editor -> Textarea
321      * Protected method that will not generally be called directly. Syncs the contents
322      * of the editor iframe with the textarea.
323      */
324     syncValue : function(){
325         if(this.initialized){
326             var bd = (this.doc.body || this.doc.documentElement);
327             //this.cleanUpPaste(); -- this is done else where and causes havoc..
328             var html = bd.innerHTML;
329             if(Roo.isSafari){
330                 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
331                 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
332                 if(m && m[1]){
333                     html = '<div style="'+m[0]+'">' + html + '</div>';
334                 }
335             }
336             html = this.cleanHtml(html);
337             // fix up the special chars.. normaly like back quotes in word...
338             // however we do not want to do this with chinese..
339             html = html.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u0080-\uFFFF]/g, function(match) {
340                 
341                 var cc = match.charCodeAt();
342
343                 // Get the character value, handling surrogate pairs
344                 if (match.length == 2) {
345                     // It's a surrogate pair, calculate the Unicode code point
346                     var high = match.charCodeAt(0) - 0xD800;
347                     var low  = match.charCodeAt(1) - 0xDC00;
348                     cc = (high * 0x400) + low + 0x10000;
349                 }  else if (
350                     (cc >= 0x4E00 && cc < 0xA000 ) ||
351                     (cc >= 0x3400 && cc < 0x4E00 ) ||
352                     (cc >= 0xf900 && cc < 0xfb00 )
353                 ) {
354                         return match;
355                 }  
356          
357                 // No, use a numeric entity. Here we brazenly (and possibly mistakenly)
358                 return "&#" + cc + ";";
359                 
360                 
361             });
362             
363             
364              
365             if(this.owner.fireEvent('beforesync', this, html) !== false){
366                 this.el.dom.value = html;
367                 this.owner.fireEvent('sync', this, html);
368             }
369         }
370     },
371
372     /**
373      * Protected method that will not generally be called directly. Pushes the value of the textarea
374      * into the iframe editor.
375      */
376     pushValue : function(){
377         if(this.initialized){
378             var v = this.el.dom.value.trim();
379             
380 //            if(v.length < 1){
381 //                v = '&#160;';
382 //            }
383             
384             if(this.owner.fireEvent('beforepush', this, v) !== false){
385                 var d = (this.doc.body || this.doc.documentElement);
386                 d.innerHTML = v;
387                 this.cleanUpPaste();
388                 this.el.dom.value = d.innerHTML;
389                 this.owner.fireEvent('push', this, v);
390             }
391         }
392     },
393
394     // private
395     deferFocus : function(){
396         this.focus.defer(10, this);
397     },
398
399     // doc'ed in Field
400     focus : function(){
401         if(this.win && !this.sourceEditMode){
402             this.win.focus();
403         }else{
404             this.el.focus();
405         }
406     },
407     
408     assignDocWin: function()
409     {
410         var iframe = this.iframe;
411         
412          if(Roo.isIE){
413             this.doc = iframe.contentWindow.document;
414             this.win = iframe.contentWindow;
415         } else {
416 //            if (!Roo.get(this.frameId)) {
417 //                return;
418 //            }
419 //            this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
420 //            this.win = Roo.get(this.frameId).dom.contentWindow;
421             
422             if (!Roo.get(this.frameId) && !iframe.contentDocument) {
423                 return;
424             }
425             
426             this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
427             this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
428         }
429     },
430     
431     // private
432     initEditor : function(){
433         //console.log("INIT EDITOR");
434         this.assignDocWin();
435         
436         
437         
438         this.doc.designMode="on";
439         this.doc.open();
440         this.doc.write(this.getDocMarkup());
441         this.doc.close();
442         
443         var dbody = (this.doc.body || this.doc.documentElement);
444         //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
445         // this copies styles from the containing element into thsi one..
446         // not sure why we need all of this..
447         //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
448         
449         //var ss = this.el.getStyles( 'background-image', 'background-repeat');
450         //ss['background-attachment'] = 'fixed'; // w3c
451         dbody.bgProperties = 'fixed'; // ie
452         //Roo.DomHelper.applyStyles(dbody, ss);
453         Roo.EventManager.on(this.doc, {
454             //'mousedown': this.onEditorEvent,
455             'mouseup': this.onEditorEvent,
456             'dblclick': this.onEditorEvent,
457             'click': this.onEditorEvent,
458             'keyup': this.onEditorEvent,
459             buffer:100,
460             scope: this
461         });
462         if(Roo.isGecko){
463             Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
464         }
465         if(Roo.isIE || Roo.isSafari || Roo.isOpera){
466             Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
467         }
468         this.initialized = true;
469
470         this.owner.fireEvent('initialize', this);
471         this.pushValue();
472     },
473
474     // private
475     onDestroy : function(){
476         
477         
478         
479         if(this.rendered){
480             
481             //for (var i =0; i < this.toolbars.length;i++) {
482             //    // fixme - ask toolbars for heights?
483             //    this.toolbars[i].onDestroy();
484            // }
485             
486             //this.wrap.dom.innerHTML = '';
487             //this.wrap.remove();
488         }
489     },
490
491     // private
492     onFirstFocus : function(){
493         
494         this.assignDocWin();
495         
496         
497         this.activated = true;
498          
499     
500         if(Roo.isGecko){ // prevent silly gecko errors
501             this.win.focus();
502             var s = this.win.getSelection();
503             if(!s.focusNode || s.focusNode.nodeType != 3){
504                 var r = s.getRangeAt(0);
505                 r.selectNodeContents((this.doc.body || this.doc.documentElement));
506                 r.collapse(true);
507                 this.deferFocus();
508             }
509             try{
510                 this.execCmd('useCSS', true);
511                 this.execCmd('styleWithCSS', false);
512             }catch(e){}
513         }
514         this.owner.fireEvent('activate', this);
515     },
516
517     // private
518     adjustFont: function(btn){
519         var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
520         //if(Roo.isSafari){ // safari
521         //    adjust *= 2;
522        // }
523         var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
524         if(Roo.isSafari){ // safari
525             var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
526             v =  (v < 10) ? 10 : v;
527             v =  (v > 48) ? 48 : v;
528             v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
529             
530         }
531         
532         
533         v = Math.max(1, v+adjust);
534         
535         this.execCmd('FontSize', v  );
536     },
537
538     onEditorEvent : function(e)
539     {
540         this.owner.fireEvent('editorevent', this, e);
541       //  this.updateToolbar();
542         this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
543     },
544
545     insertTag : function(tg)
546     {
547         // could be a bit smarter... -> wrap the current selected tRoo..
548         if (tg.toLowerCase() == 'span' ||
549             tg.toLowerCase() == 'code' ||
550             tg.toLowerCase() == 'sup' ||
551             tg.toLowerCase() == 'sub' 
552             ) {
553             
554             range = this.createRange(this.getSelection());
555             var wrappingNode = this.doc.createElement(tg.toLowerCase());
556             wrappingNode.appendChild(range.extractContents());
557             range.insertNode(wrappingNode);
558
559             return;
560             
561             
562             
563         }
564         this.execCmd("formatblock",   tg);
565         
566     },
567     
568     insertText : function(txt)
569     {
570         
571         
572         var range = this.createRange();
573         range.deleteContents();
574                //alert(Sender.getAttribute('label'));
575                
576         range.insertNode(this.doc.createTextNode(txt));
577     } ,
578     
579      
580
581     /**
582      * Executes a Midas editor command on the editor document and performs necessary focus and
583      * toolbar updates. <b>This should only be called after the editor is initialized.</b>
584      * @param {String} cmd The Midas command
585      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
586      */
587     relayCmd : function(cmd, value){
588         this.win.focus();
589         this.execCmd(cmd, value);
590         this.owner.fireEvent('editorevent', this);
591         //this.updateToolbar();
592         this.owner.deferFocus();
593     },
594
595     /**
596      * Executes a Midas editor command directly on the editor document.
597      * For visual commands, you should use {@link #relayCmd} instead.
598      * <b>This should only be called after the editor is initialized.</b>
599      * @param {String} cmd The Midas command
600      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
601      */
602     execCmd : function(cmd, value){
603         this.doc.execCommand(cmd, false, value === undefined ? null : value);
604         this.syncValue();
605     },
606  
607  
608    
609     /**
610      * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
611      * to insert tRoo.
612      * @param {String} text | dom node.. 
613      */
614     insertAtCursor : function(text)
615     {
616         
617         if(!this.activated){
618             return;
619         }
620         /*
621         if(Roo.isIE){
622             this.win.focus();
623             var r = this.doc.selection.createRange();
624             if(r){
625                 r.collapse(true);
626                 r.pasteHTML(text);
627                 this.syncValue();
628                 this.deferFocus();
629             
630             }
631             return;
632         }
633         */
634         if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
635             this.win.focus();
636             
637             
638             // from jquery ui (MIT licenced)
639             var range, node;
640             var win = this.win;
641             
642             if (win.getSelection && win.getSelection().getRangeAt) {
643                 range = win.getSelection().getRangeAt(0);
644                 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
645                 range.insertNode(node);
646             } else if (win.document.selection && win.document.selection.createRange) {
647                 // no firefox support
648                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
649                 win.document.selection.createRange().pasteHTML(txt);
650             } else {
651                 // no firefox support
652                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
653                 this.execCmd('InsertHTML', txt);
654             } 
655             
656             this.syncValue();
657             
658             this.deferFocus();
659         }
660     },
661  // private
662     mozKeyPress : function(e){
663         if(e.ctrlKey){
664             var c = e.getCharCode(), cmd;
665           
666             if(c > 0){
667                 c = String.fromCharCode(c).toLowerCase();
668                 switch(c){
669                     case 'b':
670                         cmd = 'bold';
671                         break;
672                     case 'i':
673                         cmd = 'italic';
674                         break;
675                     
676                     case 'u':
677                         cmd = 'underline';
678                         break;
679                     
680                     case 'v':
681                         this.cleanUpPaste.defer(100, this);
682                         return;
683                         
684                 }
685                 if(cmd){
686                     this.win.focus();
687                     this.execCmd(cmd);
688                     this.deferFocus();
689                     e.preventDefault();
690                 }
691                 
692             }
693         }
694     },
695
696     // private
697     fixKeys : function(){ // load time branching for fastest keydown performance
698         if(Roo.isIE){
699             return function(e){
700                 var k = e.getKey(), r;
701                 if(k == e.TAB){
702                     e.stopEvent();
703                     r = this.doc.selection.createRange();
704                     if(r){
705                         r.collapse(true);
706                         r.pasteHTML('&#160;&#160;&#160;&#160;');
707                         this.deferFocus();
708                     }
709                     return;
710                 }
711                 
712                 if(k == e.ENTER){
713                     r = this.doc.selection.createRange();
714                     if(r){
715                         var target = r.parentElement();
716                         if(!target || target.tagName.toLowerCase() != 'li'){
717                             e.stopEvent();
718                             r.pasteHTML('<br />');
719                             r.collapse(false);
720                             r.select();
721                         }
722                     }
723                 }
724                 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
725                     this.cleanUpPaste.defer(100, this);
726                     return;
727                 }
728                 
729                 
730             };
731         }else if(Roo.isOpera){
732             return function(e){
733                 var k = e.getKey();
734                 if(k == e.TAB){
735                     e.stopEvent();
736                     this.win.focus();
737                     this.execCmd('InsertHTML','&#160;&#160;&#160;&#160;');
738                     this.deferFocus();
739                 }
740                 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
741                     this.cleanUpPaste.defer(100, this);
742                     return;
743                 }
744                 
745             };
746         }else if(Roo.isSafari){
747             return function(e){
748                 var k = e.getKey();
749                 
750                 if(k == e.TAB){
751                     e.stopEvent();
752                     this.execCmd('InsertText','\t');
753                     this.deferFocus();
754                     return;
755                 }
756                if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
757                     this.cleanUpPaste.defer(100, this);
758                     return;
759                 }
760                 
761              };
762         }
763     }(),
764     
765     getAllAncestors: function()
766     {
767         var p = this.getSelectedNode();
768         var a = [];
769         if (!p) {
770             a.push(p); // push blank onto stack..
771             p = this.getParentElement();
772         }
773         
774         
775         while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
776             a.push(p);
777             p = p.parentNode;
778         }
779         a.push(this.doc.body);
780         return a;
781     },
782     lastSel : false,
783     lastSelNode : false,
784     
785     
786     getSelection : function() 
787     {
788         this.assignDocWin();
789         return Roo.isIE ? this.doc.selection : this.win.getSelection();
790     },
791     
792     getSelectedNode: function() 
793     {
794         // this may only work on Gecko!!!
795         
796         // should we cache this!!!!
797         
798         
799         
800          
801         var range = this.createRange(this.getSelection()).cloneRange();
802         
803         if (Roo.isIE) {
804             var parent = range.parentElement();
805             while (true) {
806                 var testRange = range.duplicate();
807                 testRange.moveToElementText(parent);
808                 if (testRange.inRange(range)) {
809                     break;
810                 }
811                 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
812                     break;
813                 }
814                 parent = parent.parentElement;
815             }
816             return parent;
817         }
818         
819         // is ancestor a text element.
820         var ac =  range.commonAncestorContainer;
821         if (ac.nodeType == 3) {
822             ac = ac.parentNode;
823         }
824         
825         var ar = ac.childNodes;
826          
827         var nodes = [];
828         var other_nodes = [];
829         var has_other_nodes = false;
830         for (var i=0;i<ar.length;i++) {
831             if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ? 
832                 continue;
833             }
834             // fullly contained node.
835             
836             if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
837                 nodes.push(ar[i]);
838                 continue;
839             }
840             
841             // probably selected..
842             if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
843                 other_nodes.push(ar[i]);
844                 continue;
845             }
846             // outer..
847             if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0))  {
848                 continue;
849             }
850             
851             
852             has_other_nodes = true;
853         }
854         if (!nodes.length && other_nodes.length) {
855             nodes= other_nodes;
856         }
857         if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
858             return false;
859         }
860         
861         return nodes[0];
862     },
863     createRange: function(sel)
864     {
865         // this has strange effects when using with 
866         // top toolbar - not sure if it's a great idea.
867         //this.editor.contentWindow.focus();
868         if (typeof sel != "undefined") {
869             try {
870                 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
871             } catch(e) {
872                 return this.doc.createRange();
873             }
874         } else {
875             return this.doc.createRange();
876         }
877     },
878     getParentElement: function()
879     {
880         
881         this.assignDocWin();
882         var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
883         
884         var range = this.createRange(sel);
885          
886         try {
887             var p = range.commonAncestorContainer;
888             while (p.nodeType == 3) { // text node
889                 p = p.parentNode;
890             }
891             return p;
892         } catch (e) {
893             return null;
894         }
895     
896     },
897     /***
898      *
899      * Range intersection.. the hard stuff...
900      *  '-1' = before
901      *  '0' = hits..
902      *  '1' = after.
903      *         [ -- selected range --- ]
904      *   [fail]                        [fail]
905      *
906      *    basically..
907      *      if end is before start or  hits it. fail.
908      *      if start is after end or hits it fail.
909      *
910      *   if either hits (but other is outside. - then it's not 
911      *   
912      *    
913      **/
914     
915     
916     // @see http://www.thismuchiknow.co.uk/?p=64.
917     rangeIntersectsNode : function(range, node)
918     {
919         var nodeRange = node.ownerDocument.createRange();
920         try {
921             nodeRange.selectNode(node);
922         } catch (e) {
923             nodeRange.selectNodeContents(node);
924         }
925     
926         var rangeStartRange = range.cloneRange();
927         rangeStartRange.collapse(true);
928     
929         var rangeEndRange = range.cloneRange();
930         rangeEndRange.collapse(false);
931     
932         var nodeStartRange = nodeRange.cloneRange();
933         nodeStartRange.collapse(true);
934     
935         var nodeEndRange = nodeRange.cloneRange();
936         nodeEndRange.collapse(false);
937     
938         return rangeStartRange.compareBoundaryPoints(
939                  Range.START_TO_START, nodeEndRange) == -1 &&
940                rangeEndRange.compareBoundaryPoints(
941                  Range.START_TO_START, nodeStartRange) == 1;
942         
943          
944     },
945     rangeCompareNode : function(range, node)
946     {
947         var nodeRange = node.ownerDocument.createRange();
948         try {
949             nodeRange.selectNode(node);
950         } catch (e) {
951             nodeRange.selectNodeContents(node);
952         }
953         
954         
955         range.collapse(true);
956     
957         nodeRange.collapse(true);
958      
959         var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
960         var ee = range.compareBoundaryPoints(  Range.END_TO_END, nodeRange);
961          
962         //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
963         
964         var nodeIsBefore   =  ss == 1;
965         var nodeIsAfter    = ee == -1;
966         
967         if (nodeIsBefore && nodeIsAfter) {
968             return 0; // outer
969         }
970         if (!nodeIsBefore && nodeIsAfter) {
971             return 1; //right trailed.
972         }
973         
974         if (nodeIsBefore && !nodeIsAfter) {
975             return 2;  // left trailed.
976         }
977         // fully contined.
978         return 3;
979     },
980
981     // private? - in a new class?
982     cleanUpPaste :  function()
983     {
984         // cleans up the whole document..
985         Roo.log('cleanuppaste');
986         
987         this.cleanUpChildren(this.doc.body);
988         var clean = this.cleanWordChars(this.doc.body.innerHTML);
989         if (clean != this.doc.body.innerHTML) {
990             this.doc.body.innerHTML = clean;
991         }
992         
993     },
994     
995     cleanWordChars : function(input) {// change the chars to hex code
996         var he = Roo.HtmlEditorCore;
997         
998         var output = input;
999         Roo.each(he.swapCodes, function(sw) { 
1000             var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1001             
1002             output = output.replace(swapper, sw[1]);
1003         });
1004         
1005         return output;
1006     },
1007     
1008     
1009     cleanUpChildren : function (n)
1010     {
1011         if (!n.childNodes.length) {
1012             return;
1013         }
1014         for (var i = n.childNodes.length-1; i > -1 ; i--) {
1015            this.cleanUpChild(n.childNodes[i]);
1016         }
1017     },
1018     
1019     
1020         
1021     
1022     cleanUpChild : function (node)
1023     {
1024         var ed = this;
1025         //console.log(node);
1026         if (node.nodeName == "#text") {
1027             // clean up silly Windows -- stuff?
1028             return; 
1029         }
1030         if (node.nodeName == "#comment") {
1031             node.parentNode.removeChild(node);
1032             // clean up silly Windows -- stuff?
1033             return; 
1034         }
1035         var lcname = node.tagName.toLowerCase();
1036         // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full
1037         // whitelist of tags..
1038         
1039         if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
1040             // remove node.
1041             node.parentNode.removeChild(node);
1042             return;
1043             
1044         }
1045         
1046         var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1047         
1048         // spans with no attributes - just remove them..
1049         if ((!node.attributes || !node.attributes.length) && lcname == 'span') { 
1050             remove_keep_children = true;
1051         }
1052         
1053         // remove <a name=....> as rendering on yahoo mailer is borked with this.
1054         // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1055         
1056         //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1057         //    remove_keep_children = true;
1058         //}
1059         
1060         if (remove_keep_children) {
1061             this.cleanUpChildren(node);
1062             // inserts everything just before this node...
1063             while (node.childNodes.length) {
1064                 var cn = node.childNodes[0];
1065                 node.removeChild(cn);
1066                 node.parentNode.insertBefore(cn, node);
1067             }
1068             node.parentNode.removeChild(node);
1069             return;
1070         }
1071         
1072         if (!node.attributes || !node.attributes.length) {
1073             
1074           
1075             
1076             
1077             this.cleanUpChildren(node);
1078             return;
1079         }
1080         
1081         function cleanAttr(n,v)
1082         {
1083             
1084             if (v.match(/^\./) || v.match(/^\//)) {
1085                 return;
1086             }
1087             if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/) || v.match(/^ftp:/)) {
1088                 return;
1089             }
1090             if (v.match(/^#/)) {
1091                 return;
1092             }
1093             if (v.match(/^\{/)) { // allow template editing.
1094                 return;
1095             }
1096 //            Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1097             node.removeAttribute(n);
1098             
1099         }
1100         
1101         var cwhite = this.cwhite;
1102         var cblack = this.cblack;
1103             
1104         function cleanStyle(n,v)
1105         {
1106             if (v.match(/expression/)) { //XSS?? should we even bother..
1107                 node.removeAttribute(n);
1108                 return;
1109             }
1110             
1111             var parts = v.split(/;/);
1112             var clean = [];
1113             
1114             Roo.each(parts, function(p) {
1115                 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1116                 if (!p.length) {
1117                     return true;
1118                 }
1119                 var l = p.split(':').shift().replace(/\s+/g,'');
1120                 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1121                 
1122                 if ( cwhite.length && cblack.indexOf(l) > -1) {
1123 //                    Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1124                     //node.removeAttribute(n);
1125                     return true;
1126                 }
1127                 //Roo.log()
1128                 // only allow 'c whitelisted system attributes'
1129                 if ( cwhite.length &&  cwhite.indexOf(l) < 0) {
1130 //                    Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1131                     //node.removeAttribute(n);
1132                     return true;
1133                 }
1134                 
1135                 
1136                  
1137                 
1138                 clean.push(p);
1139                 return true;
1140             });
1141             if (clean.length) { 
1142                 node.setAttribute(n, clean.join(';'));
1143             } else {
1144                 node.removeAttribute(n);
1145             }
1146             
1147         }
1148         
1149         
1150         for (var i = node.attributes.length-1; i > -1 ; i--) {
1151             var a = node.attributes[i];
1152             //console.log(a);
1153             
1154             if (a.name.toLowerCase().substr(0,2)=='on')  {
1155                 node.removeAttribute(a.name);
1156                 continue;
1157             }
1158             if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1159                 node.removeAttribute(a.name);
1160                 continue;
1161             }
1162             if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1163                 cleanAttr(a.name,a.value); // fixme..
1164                 continue;
1165             }
1166             if (a.name == 'style') {
1167                 cleanStyle(a.name,a.value);
1168                 continue;
1169             }
1170             /// clean up MS crap..
1171             // tecnically this should be a list of valid class'es..
1172             
1173             
1174             if (a.name == 'class') {
1175                 if (a.value.match(/^Mso/)) {
1176                     node.removeAttribute('class');
1177                 }
1178                 
1179                 if (a.value.match(/^body$/)) {
1180                     node.removeAttribute('class');
1181                 }
1182                 continue;
1183             }
1184             
1185             // style cleanup!?
1186             // class cleanup?
1187             
1188         }
1189         
1190         
1191         this.cleanUpChildren(node);
1192         
1193         
1194     },
1195     
1196     /**
1197      * Clean up MS wordisms...
1198      */
1199     cleanWord : function(node)
1200     {
1201         if (!node) {
1202             this.cleanWord(this.doc.body);
1203             return;
1204         }
1205         
1206         if(
1207                 node.nodeName == 'SPAN' &&
1208                 !node.hasAttributes() &&
1209                 node.childNodes.length == 1 &&
1210                 node.firstChild.nodeName == "#text"  
1211         ) {
1212             var textNode = node.firstChild;
1213             node.removeChild(textNode);
1214             if (node.getAttribute('lang') != 'zh-CN') {   // do not space pad on chinese characters..
1215                 node.parentNode.insertBefore(node.ownerDocument.createTextNode(" "), node);
1216             }
1217             node.parentNode.insertBefore(textNode, node);
1218             if (node.getAttribute('lang') != 'zh-CN') {   // do not space pad on chinese characters..
1219                 node.parentNode.insertBefore(node.ownerDocument.createTextNode(" ") , node);
1220             }
1221             node.parentNode.removeChild(node);
1222         }
1223         
1224         if (node.nodeName == "#text") {
1225             // clean up silly Windows -- stuff?
1226             return; 
1227         }
1228         if (node.nodeName == "#comment") {
1229             node.parentNode.removeChild(node);
1230             // clean up silly Windows -- stuff?
1231             return; 
1232         }
1233         
1234         if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1235             node.parentNode.removeChild(node);
1236             return;
1237         }
1238         //Roo.log(node.tagName);
1239         // remove - but keep children..
1240         if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|v:|font)/)) {
1241             //Roo.log('-- removed');
1242             while (node.childNodes.length) {
1243                 var cn = node.childNodes[0];
1244                 node.removeChild(cn);
1245                 node.parentNode.insertBefore(cn, node);
1246                 // move node to parent - and clean it..
1247                 this.cleanWord(cn);
1248             }
1249             node.parentNode.removeChild(node);
1250             /// no need to iterate chidlren = it's got none..
1251             //this.iterateChildren(node, this.cleanWord);
1252             return;
1253         }
1254         // clean styles
1255         if (node.className.length) {
1256             
1257             var cn = node.className.split(/\W+/);
1258             var cna = [];
1259             Roo.each(cn, function(cls) {
1260                 if (cls.match(/Mso[a-zA-Z]+/)) {
1261                     return;
1262                 }
1263                 cna.push(cls);
1264             });
1265             node.className = cna.length ? cna.join(' ') : '';
1266             if (!cna.length) {
1267                 node.removeAttribute("class");
1268             }
1269         }
1270         
1271         if (node.hasAttribute("lang")) {
1272             node.removeAttribute("lang");
1273         }
1274         
1275         if (node.hasAttribute("style")) {
1276             
1277             var styles = node.getAttribute("style").split(";");
1278             var nstyle = [];
1279             Roo.each(styles, function(s) {
1280                 if (!s.match(/:/)) {
1281                     return;
1282                 }
1283                 var kv = s.split(":");
1284                 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1285                     return;
1286                 }
1287                 // what ever is left... we allow.
1288                 nstyle.push(s);
1289             });
1290             node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1291             if (!nstyle.length) {
1292                 node.removeAttribute('style');
1293             }
1294         }
1295         this.iterateChildren(node, this.cleanWord);
1296         
1297         
1298         
1299     },
1300     /**
1301      * iterateChildren of a Node, calling fn each time, using this as the scole..
1302      * @param {DomNode} node node to iterate children of.
1303      * @param {Function} fn method of this class to call on each item.
1304      */
1305     iterateChildren : function(node, fn)
1306     {
1307         if (!node.childNodes.length) {
1308                 return;
1309         }
1310         for (var i = node.childNodes.length-1; i > -1 ; i--) {
1311            fn.call(this, node.childNodes[i])
1312         }
1313     },
1314     
1315     
1316     /**
1317      * cleanTableWidths.
1318      *
1319      * Quite often pasting from word etc.. results in tables with column and widths.
1320      * This does not work well on fluid HTML layouts - like emails. - so this code should hunt an destroy them..
1321      *
1322      */
1323     cleanTableWidths : function(node)
1324     {
1325          
1326          
1327         if (!node) {
1328             this.cleanTableWidths(this.doc.body);
1329             return;
1330         }
1331         
1332         // ignore list...
1333         if (node.nodeName == "#text" || node.nodeName == "#comment") {
1334             return; 
1335         }
1336         Roo.log(node.tagName);
1337         if (!node.tagName.toLowerCase().match(/^(table|td|tr)$/)) {
1338             this.iterateChildren(node, this.cleanTableWidths);
1339             return;
1340         }
1341         if (node.hasAttribute('width')) {
1342             node.removeAttribute('width');
1343         }
1344         
1345          
1346         if (node.hasAttribute("style")) {
1347             // pretty basic...
1348             
1349             var styles = node.getAttribute("style").split(";");
1350             var nstyle = [];
1351             Roo.each(styles, function(s) {
1352                 if (!s.match(/:/)) {
1353                     return;
1354                 }
1355                 var kv = s.split(":");
1356                 if (kv[0].match(/^\s*(width|min-width)\s*$/)) {
1357                     return;
1358                 }
1359                 // what ever is left... we allow.
1360                 nstyle.push(s);
1361             });
1362             node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1363             if (!nstyle.length) {
1364                 node.removeAttribute('style');
1365             }
1366         }
1367         
1368         this.iterateChildren(node, this.cleanTableWidths);
1369         
1370         
1371     },
1372     
1373     
1374     
1375     
1376     domToHTML : function(currentElement, depth, nopadtext) {
1377         
1378         depth = depth || 0;
1379         nopadtext = nopadtext || false;
1380     
1381         if (!currentElement) {
1382             return this.domToHTML(this.doc.body);
1383         }
1384         
1385         //Roo.log(currentElement);
1386         var j;
1387         var allText = false;
1388         var nodeName = currentElement.nodeName;
1389         var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1390         
1391         if  (nodeName == '#text') {
1392             
1393             return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim();
1394         }
1395         
1396         
1397         var ret = '';
1398         if (nodeName != 'BODY') {
1399              
1400             var i = 0;
1401             // Prints the node tagName, such as <A>, <IMG>, etc
1402             if (tagName) {
1403                 var attr = [];
1404                 for(i = 0; i < currentElement.attributes.length;i++) {
1405                     // quoting?
1406                     var aname = currentElement.attributes.item(i).name;
1407                     if (!currentElement.attributes.item(i).value.length) {
1408                         continue;
1409                     }
1410                     attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1411                 }
1412                 
1413                 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1414             } 
1415             else {
1416                 
1417                 // eack
1418             }
1419         } else {
1420             tagName = false;
1421         }
1422         if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1423             return ret;
1424         }
1425         if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1426             nopadtext = true;
1427         }
1428         
1429         
1430         // Traverse the tree
1431         i = 0;
1432         var currentElementChild = currentElement.childNodes.item(i);
1433         var allText = true;
1434         var innerHTML  = '';
1435         lastnode = '';
1436         while (currentElementChild) {
1437             // Formatting code (indent the tree so it looks nice on the screen)
1438             var nopad = nopadtext;
1439             if (lastnode == 'SPAN') {
1440                 nopad  = true;
1441             }
1442             // text
1443             if  (currentElementChild.nodeName == '#text') {
1444                 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1445                 toadd = nopadtext ? toadd : toadd.trim();
1446                 if (!nopad && toadd.length > 80) {
1447                     innerHTML  += "\n" + (new Array( depth + 1 )).join( "  "  );
1448                 }
1449                 innerHTML  += toadd;
1450                 
1451                 i++;
1452                 currentElementChild = currentElement.childNodes.item(i);
1453                 lastNode = '';
1454                 continue;
1455             }
1456             allText = false;
1457             
1458             innerHTML  += nopad ? '' : "\n" + (new Array( depth + 1 )).join( "  "  );
1459                 
1460             // Recursively traverse the tree structure of the child node
1461             innerHTML   += this.domToHTML(currentElementChild, depth+1, nopadtext);
1462             lastnode = currentElementChild.nodeName;
1463             i++;
1464             currentElementChild=currentElement.childNodes.item(i);
1465         }
1466         
1467         ret += innerHTML;
1468         
1469         if (!allText) {
1470                 // The remaining code is mostly for formatting the tree
1471             ret+= nopadtext ? '' : "\n" + (new Array( depth  )).join( "  "  );
1472         }
1473         
1474         
1475         if (tagName) {
1476             ret+= "</"+tagName+">";
1477         }
1478         return ret;
1479         
1480     },
1481         
1482     applyBlacklists : function()
1483     {
1484         var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white  : [];
1485         var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black :  [];
1486         
1487         this.white = [];
1488         this.black = [];
1489         Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1490             if (b.indexOf(tag) > -1) {
1491                 return;
1492             }
1493             this.white.push(tag);
1494             
1495         }, this);
1496         
1497         Roo.each(w, function(tag) {
1498             if (b.indexOf(tag) > -1) {
1499                 return;
1500             }
1501             if (this.white.indexOf(tag) > -1) {
1502                 return;
1503             }
1504             this.white.push(tag);
1505             
1506         }, this);
1507         
1508         
1509         Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1510             if (w.indexOf(tag) > -1) {
1511                 return;
1512             }
1513             this.black.push(tag);
1514             
1515         }, this);
1516         
1517         Roo.each(b, function(tag) {
1518             if (w.indexOf(tag) > -1) {
1519                 return;
1520             }
1521             if (this.black.indexOf(tag) > -1) {
1522                 return;
1523             }
1524             this.black.push(tag);
1525             
1526         }, this);
1527         
1528         
1529         w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite  : [];
1530         b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack :  [];
1531         
1532         this.cwhite = [];
1533         this.cblack = [];
1534         Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1535             if (b.indexOf(tag) > -1) {
1536                 return;
1537             }
1538             this.cwhite.push(tag);
1539             
1540         }, this);
1541         
1542         Roo.each(w, function(tag) {
1543             if (b.indexOf(tag) > -1) {
1544                 return;
1545             }
1546             if (this.cwhite.indexOf(tag) > -1) {
1547                 return;
1548             }
1549             this.cwhite.push(tag);
1550             
1551         }, this);
1552         
1553         
1554         Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1555             if (w.indexOf(tag) > -1) {
1556                 return;
1557             }
1558             this.cblack.push(tag);
1559             
1560         }, this);
1561         
1562         Roo.each(b, function(tag) {
1563             if (w.indexOf(tag) > -1) {
1564                 return;
1565             }
1566             if (this.cblack.indexOf(tag) > -1) {
1567                 return;
1568             }
1569             this.cblack.push(tag);
1570             
1571         }, this);
1572     },
1573     
1574     setStylesheets : function(stylesheets)
1575     {
1576         if(typeof(stylesheets) == 'string'){
1577             Roo.get(this.iframe.contentDocument.head).createChild({
1578                 tag : 'link',
1579                 rel : 'stylesheet',
1580                 type : 'text/css',
1581                 href : stylesheets
1582             });
1583             
1584             return;
1585         }
1586         var _this = this;
1587      
1588         Roo.each(stylesheets, function(s) {
1589             if(!s.length){
1590                 return;
1591             }
1592             
1593             Roo.get(_this.iframe.contentDocument.head).createChild({
1594                 tag : 'link',
1595                 rel : 'stylesheet',
1596                 type : 'text/css',
1597                 href : s
1598             });
1599         });
1600
1601         
1602     },
1603     
1604     removeStylesheets : function()
1605     {
1606         var _this = this;
1607         
1608         Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1609             s.remove();
1610         });
1611     },
1612     
1613     setStyle : function(style)
1614     {
1615         Roo.get(this.iframe.contentDocument.head).createChild({
1616             tag : 'style',
1617             type : 'text/css',
1618             html : style
1619         });
1620
1621         return;
1622     }
1623     
1624     // hide stuff that is not compatible
1625     /**
1626      * @event blur
1627      * @hide
1628      */
1629     /**
1630      * @event change
1631      * @hide
1632      */
1633     /**
1634      * @event focus
1635      * @hide
1636      */
1637     /**
1638      * @event specialkey
1639      * @hide
1640      */
1641     /**
1642      * @cfg {String} fieldClass @hide
1643      */
1644     /**
1645      * @cfg {String} focusClass @hide
1646      */
1647     /**
1648      * @cfg {String} autoCreate @hide
1649      */
1650     /**
1651      * @cfg {String} inputType @hide
1652      */
1653     /**
1654      * @cfg {String} invalidClass @hide
1655      */
1656     /**
1657      * @cfg {String} invalidText @hide
1658      */
1659     /**
1660      * @cfg {String} msgFx @hide
1661      */
1662     /**
1663      * @cfg {String} validateOnBlur @hide
1664      */
1665 });
1666
1667 Roo.HtmlEditorCore.white = [
1668         'area', 'br', 'img', 'input', 'hr', 'wbr',
1669         
1670        'address', 'blockquote', 'center', 'dd',      'dir',       'div', 
1671        'dl',      'dt',         'h1',     'h2',      'h3',        'h4', 
1672        'h5',      'h6',         'hr',     'isindex', 'listing',   'marquee', 
1673        'menu',    'multicol',   'ol',     'p',       'plaintext', 'pre', 
1674        'table',   'ul',         'xmp', 
1675        
1676        'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', 
1677       'thead',   'tr', 
1678      
1679       'dir', 'menu', 'ol', 'ul', 'dl',
1680        
1681       'embed',  'object'
1682 ];
1683
1684
1685 Roo.HtmlEditorCore.black = [
1686     //    'embed',  'object', // enable - backend responsiblity to clean thiese
1687         'applet', // 
1688         'base',   'basefont', 'bgsound', 'blink',  'body', 
1689         'frame',  'frameset', 'head',    'html',   'ilayer', 
1690         'iframe', 'layer',  'link',     'meta',    'object',   
1691         'script', 'style' ,'title',  'xml' // clean later..
1692 ];
1693 Roo.HtmlEditorCore.clean = [
1694     'script', 'style', 'title', 'xml'
1695 ];
1696 Roo.HtmlEditorCore.remove = [
1697     'font'
1698 ];
1699 // attributes..
1700
1701 Roo.HtmlEditorCore.ablack = [
1702     'on'
1703 ];
1704     
1705 Roo.HtmlEditorCore.aclean = [ 
1706     'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc' 
1707 ];
1708
1709 // protocols..
1710 Roo.HtmlEditorCore.pwhite= [
1711         'http',  'https',  'mailto'
1712 ];
1713
1714 // white listed style attributes.
1715 Roo.HtmlEditorCore.cwhite= [
1716       //  'text-align', /// default is to allow most things..
1717       
1718          
1719 //        'font-size'//??
1720 ];
1721
1722 // black listed style attributes.
1723 Roo.HtmlEditorCore.cblack= [
1724       //  'font-size' -- this can be set by the project 
1725 ];
1726
1727
1728 Roo.HtmlEditorCore.swapCodes   =[ 
1729     [    8211, "&#8211;" ], 
1730     [    8212, "&#8212;" ], 
1731     [    8216,  "'" ],  
1732     [    8217, "'" ],  
1733     [    8220, '"' ],  
1734     [    8221, '"' ],  
1735     [    8226, "*" ],  
1736     [    8230, "..." ]
1737 ]; 
1738
1739