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