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