Roo/HtmlEditorCore.js
[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' || tg.toLowerCase() == 'code') {
548             
549             range = this.createRange(this.getSelection());
550             var wrappingNode = this.doc.createElement(tg.toLowerCase());
551             wrappingNode.appendChild(range.extractContents());
552             range.insertNode(wrappingNode);
553
554             return;
555             
556             
557             
558         }
559         this.execCmd("formatblock",   tg);
560         
561     },
562     
563     insertText : function(txt)
564     {
565         
566         
567         var range = this.createRange();
568         range.deleteContents();
569                //alert(Sender.getAttribute('label'));
570                
571         range.insertNode(this.doc.createTextNode(txt));
572     } ,
573     
574      
575
576     /**
577      * Executes a Midas editor command on the editor document and performs necessary focus and
578      * toolbar updates. <b>This should only be called after the editor is initialized.</b>
579      * @param {String} cmd The Midas command
580      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
581      */
582     relayCmd : function(cmd, value){
583         this.win.focus();
584         this.execCmd(cmd, value);
585         this.owner.fireEvent('editorevent', this);
586         //this.updateToolbar();
587         this.owner.deferFocus();
588     },
589
590     /**
591      * Executes a Midas editor command directly on the editor document.
592      * For visual commands, you should use {@link #relayCmd} instead.
593      * <b>This should only be called after the editor is initialized.</b>
594      * @param {String} cmd The Midas command
595      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
596      */
597     execCmd : function(cmd, value){
598         this.doc.execCommand(cmd, false, value === undefined ? null : value);
599         this.syncValue();
600     },
601  
602  
603    
604     /**
605      * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
606      * to insert tRoo.
607      * @param {String} text | dom node.. 
608      */
609     insertAtCursor : function(text)
610     {
611         
612         if(!this.activated){
613             return;
614         }
615         /*
616         if(Roo.isIE){
617             this.win.focus();
618             var r = this.doc.selection.createRange();
619             if(r){
620                 r.collapse(true);
621                 r.pasteHTML(text);
622                 this.syncValue();
623                 this.deferFocus();
624             
625             }
626             return;
627         }
628         */
629         if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
630             this.win.focus();
631             
632             
633             // from jquery ui (MIT licenced)
634             var range, node;
635             var win = this.win;
636             
637             if (win.getSelection && win.getSelection().getRangeAt) {
638                 range = win.getSelection().getRangeAt(0);
639                 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
640                 range.insertNode(node);
641             } else if (win.document.selection && win.document.selection.createRange) {
642                 // no firefox support
643                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
644                 win.document.selection.createRange().pasteHTML(txt);
645             } else {
646                 // no firefox support
647                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
648                 this.execCmd('InsertHTML', txt);
649             } 
650             
651             this.syncValue();
652             
653             this.deferFocus();
654         }
655     },
656  // private
657     mozKeyPress : function(e){
658         if(e.ctrlKey){
659             var c = e.getCharCode(), cmd;
660           
661             if(c > 0){
662                 c = String.fromCharCode(c).toLowerCase();
663                 switch(c){
664                     case 'b':
665                         cmd = 'bold';
666                         break;
667                     case 'i':
668                         cmd = 'italic';
669                         break;
670                     
671                     case 'u':
672                         cmd = 'underline';
673                         break;
674                     
675                     case 'v':
676                         this.cleanUpPaste.defer(100, this);
677                         return;
678                         
679                 }
680                 if(cmd){
681                     this.win.focus();
682                     this.execCmd(cmd);
683                     this.deferFocus();
684                     e.preventDefault();
685                 }
686                 
687             }
688         }
689     },
690
691     // private
692     fixKeys : function(){ // load time branching for fastest keydown performance
693         if(Roo.isIE){
694             return function(e){
695                 var k = e.getKey(), r;
696                 if(k == e.TAB){
697                     e.stopEvent();
698                     r = this.doc.selection.createRange();
699                     if(r){
700                         r.collapse(true);
701                         r.pasteHTML('&#160;&#160;&#160;&#160;');
702                         this.deferFocus();
703                     }
704                     return;
705                 }
706                 
707                 if(k == e.ENTER){
708                     r = this.doc.selection.createRange();
709                     if(r){
710                         var target = r.parentElement();
711                         if(!target || target.tagName.toLowerCase() != 'li'){
712                             e.stopEvent();
713                             r.pasteHTML('<br />');
714                             r.collapse(false);
715                             r.select();
716                         }
717                     }
718                 }
719                 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
720                     this.cleanUpPaste.defer(100, this);
721                     return;
722                 }
723                 
724                 
725             };
726         }else if(Roo.isOpera){
727             return function(e){
728                 var k = e.getKey();
729                 if(k == e.TAB){
730                     e.stopEvent();
731                     this.win.focus();
732                     this.execCmd('InsertHTML','&#160;&#160;&#160;&#160;');
733                     this.deferFocus();
734                 }
735                 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
736                     this.cleanUpPaste.defer(100, this);
737                     return;
738                 }
739                 
740             };
741         }else if(Roo.isSafari){
742             return function(e){
743                 var k = e.getKey();
744                 
745                 if(k == e.TAB){
746                     e.stopEvent();
747                     this.execCmd('InsertText','\t');
748                     this.deferFocus();
749                     return;
750                 }
751                if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
752                     this.cleanUpPaste.defer(100, this);
753                     return;
754                 }
755                 
756              };
757         }
758     }(),
759     
760     getAllAncestors: function()
761     {
762         var p = this.getSelectedNode();
763         var a = [];
764         if (!p) {
765             a.push(p); // push blank onto stack..
766             p = this.getParentElement();
767         }
768         
769         
770         while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
771             a.push(p);
772             p = p.parentNode;
773         }
774         a.push(this.doc.body);
775         return a;
776     },
777     lastSel : false,
778     lastSelNode : false,
779     
780     
781     getSelection : function() 
782     {
783         this.assignDocWin();
784         return Roo.isIE ? this.doc.selection : this.win.getSelection();
785     },
786     
787     getSelectedNode: function() 
788     {
789         // this may only work on Gecko!!!
790         
791         // should we cache this!!!!
792         
793         
794         
795          
796         var range = this.createRange(this.getSelection()).cloneRange();
797         
798         if (Roo.isIE) {
799             var parent = range.parentElement();
800             while (true) {
801                 var testRange = range.duplicate();
802                 testRange.moveToElementText(parent);
803                 if (testRange.inRange(range)) {
804                     break;
805                 }
806                 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
807                     break;
808                 }
809                 parent = parent.parentElement;
810             }
811             return parent;
812         }
813         
814         // is ancestor a text element.
815         var ac =  range.commonAncestorContainer;
816         if (ac.nodeType == 3) {
817             ac = ac.parentNode;
818         }
819         
820         var ar = ac.childNodes;
821          
822         var nodes = [];
823         var other_nodes = [];
824         var has_other_nodes = false;
825         for (var i=0;i<ar.length;i++) {
826             if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ? 
827                 continue;
828             }
829             // fullly contained node.
830             
831             if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
832                 nodes.push(ar[i]);
833                 continue;
834             }
835             
836             // probably selected..
837             if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
838                 other_nodes.push(ar[i]);
839                 continue;
840             }
841             // outer..
842             if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0))  {
843                 continue;
844             }
845             
846             
847             has_other_nodes = true;
848         }
849         if (!nodes.length && other_nodes.length) {
850             nodes= other_nodes;
851         }
852         if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
853             return false;
854         }
855         
856         return nodes[0];
857     },
858     createRange: function(sel)
859     {
860         // this has strange effects when using with 
861         // top toolbar - not sure if it's a great idea.
862         //this.editor.contentWindow.focus();
863         if (typeof sel != "undefined") {
864             try {
865                 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
866             } catch(e) {
867                 return this.doc.createRange();
868             }
869         } else {
870             return this.doc.createRange();
871         }
872     },
873     getParentElement: function()
874     {
875         
876         this.assignDocWin();
877         var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
878         
879         var range = this.createRange(sel);
880          
881         try {
882             var p = range.commonAncestorContainer;
883             while (p.nodeType == 3) { // text node
884                 p = p.parentNode;
885             }
886             return p;
887         } catch (e) {
888             return null;
889         }
890     
891     },
892     /***
893      *
894      * Range intersection.. the hard stuff...
895      *  '-1' = before
896      *  '0' = hits..
897      *  '1' = after.
898      *         [ -- selected range --- ]
899      *   [fail]                        [fail]
900      *
901      *    basically..
902      *      if end is before start or  hits it. fail.
903      *      if start is after end or hits it fail.
904      *
905      *   if either hits (but other is outside. - then it's not 
906      *   
907      *    
908      **/
909     
910     
911     // @see http://www.thismuchiknow.co.uk/?p=64.
912     rangeIntersectsNode : function(range, node)
913     {
914         var nodeRange = node.ownerDocument.createRange();
915         try {
916             nodeRange.selectNode(node);
917         } catch (e) {
918             nodeRange.selectNodeContents(node);
919         }
920     
921         var rangeStartRange = range.cloneRange();
922         rangeStartRange.collapse(true);
923     
924         var rangeEndRange = range.cloneRange();
925         rangeEndRange.collapse(false);
926     
927         var nodeStartRange = nodeRange.cloneRange();
928         nodeStartRange.collapse(true);
929     
930         var nodeEndRange = nodeRange.cloneRange();
931         nodeEndRange.collapse(false);
932     
933         return rangeStartRange.compareBoundaryPoints(
934                  Range.START_TO_START, nodeEndRange) == -1 &&
935                rangeEndRange.compareBoundaryPoints(
936                  Range.START_TO_START, nodeStartRange) == 1;
937         
938          
939     },
940     rangeCompareNode : function(range, node)
941     {
942         var nodeRange = node.ownerDocument.createRange();
943         try {
944             nodeRange.selectNode(node);
945         } catch (e) {
946             nodeRange.selectNodeContents(node);
947         }
948         
949         
950         range.collapse(true);
951     
952         nodeRange.collapse(true);
953      
954         var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
955         var ee = range.compareBoundaryPoints(  Range.END_TO_END, nodeRange);
956          
957         //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
958         
959         var nodeIsBefore   =  ss == 1;
960         var nodeIsAfter    = ee == -1;
961         
962         if (nodeIsBefore && nodeIsAfter) {
963             return 0; // outer
964         }
965         if (!nodeIsBefore && nodeIsAfter) {
966             return 1; //right trailed.
967         }
968         
969         if (nodeIsBefore && !nodeIsAfter) {
970             return 2;  // left trailed.
971         }
972         // fully contined.
973         return 3;
974     },
975
976     // private? - in a new class?
977     cleanUpPaste :  function()
978     {
979         // cleans up the whole document..
980         Roo.log('cleanuppaste');
981         
982         this.cleanUpChildren(this.doc.body);
983         var clean = this.cleanWordChars(this.doc.body.innerHTML);
984         if (clean != this.doc.body.innerHTML) {
985             this.doc.body.innerHTML = clean;
986         }
987         
988     },
989     
990     cleanWordChars : function(input) {// change the chars to hex code
991         var he = Roo.HtmlEditorCore;
992         
993         var output = input;
994         Roo.each(he.swapCodes, function(sw) { 
995             var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
996             
997             output = output.replace(swapper, sw[1]);
998         });
999         
1000         return output;
1001     },
1002     
1003     
1004     cleanUpChildren : function (n)
1005     {
1006         if (!n.childNodes.length) {
1007             return;
1008         }
1009         for (var i = n.childNodes.length-1; i > -1 ; i--) {
1010            this.cleanUpChild(n.childNodes[i]);
1011         }
1012     },
1013     
1014     
1015         
1016     
1017     cleanUpChild : function (node)
1018     {
1019         var ed = this;
1020         //console.log(node);
1021         if (node.nodeName == "#text") {
1022             // clean up silly Windows -- stuff?
1023             return; 
1024         }
1025         if (node.nodeName == "#comment") {
1026             node.parentNode.removeChild(node);
1027             // clean up silly Windows -- stuff?
1028             return; 
1029         }
1030         var lcname = node.tagName.toLowerCase();
1031         // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full
1032         // whitelist of tags..
1033         
1034         if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
1035             // remove node.
1036             node.parentNode.removeChild(node);
1037             return;
1038             
1039         }
1040         
1041         var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1042         
1043         // spans with no attributes - just remove them..
1044         if ((!node.attributes || !node.attributes.length) && lcname == 'span') { 
1045             remove_keep_children = true;
1046         }
1047         
1048         // remove <a name=....> as rendering on yahoo mailer is borked with this.
1049         // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1050         
1051         //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1052         //    remove_keep_children = true;
1053         //}
1054         
1055         if (remove_keep_children) {
1056             this.cleanUpChildren(node);
1057             // inserts everything just before this node...
1058             while (node.childNodes.length) {
1059                 var cn = node.childNodes[0];
1060                 node.removeChild(cn);
1061                 node.parentNode.insertBefore(cn, node);
1062             }
1063             node.parentNode.removeChild(node);
1064             return;
1065         }
1066         
1067         if (!node.attributes || !node.attributes.length) {
1068             
1069           
1070             
1071             
1072             this.cleanUpChildren(node);
1073             return;
1074         }
1075         
1076         function cleanAttr(n,v)
1077         {
1078             
1079             if (v.match(/^\./) || v.match(/^\//)) {
1080                 return;
1081             }
1082             if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/) || v.match(/^ftp:/)) {
1083                 return;
1084             }
1085             if (v.match(/^#/)) {
1086                 return;
1087             }
1088 //            Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1089             node.removeAttribute(n);
1090             
1091         }
1092         
1093         var cwhite = this.cwhite;
1094         var cblack = this.cblack;
1095             
1096         function cleanStyle(n,v)
1097         {
1098             if (v.match(/expression/)) { //XSS?? should we even bother..
1099                 node.removeAttribute(n);
1100                 return;
1101             }
1102             
1103             var parts = v.split(/;/);
1104             var clean = [];
1105             
1106             Roo.each(parts, function(p) {
1107                 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1108                 if (!p.length) {
1109                     return true;
1110                 }
1111                 var l = p.split(':').shift().replace(/\s+/g,'');
1112                 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1113                 
1114                 if ( cwhite.length && cblack.indexOf(l) > -1) {
1115 //                    Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1116                     //node.removeAttribute(n);
1117                     return true;
1118                 }
1119                 //Roo.log()
1120                 // only allow 'c whitelisted system attributes'
1121                 if ( cwhite.length &&  cwhite.indexOf(l) < 0) {
1122 //                    Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1123                     //node.removeAttribute(n);
1124                     return true;
1125                 }
1126                 
1127                 
1128                  
1129                 
1130                 clean.push(p);
1131                 return true;
1132             });
1133             if (clean.length) { 
1134                 node.setAttribute(n, clean.join(';'));
1135             } else {
1136                 node.removeAttribute(n);
1137             }
1138             
1139         }
1140         
1141         
1142         for (var i = node.attributes.length-1; i > -1 ; i--) {
1143             var a = node.attributes[i];
1144             //console.log(a);
1145             
1146             if (a.name.toLowerCase().substr(0,2)=='on')  {
1147                 node.removeAttribute(a.name);
1148                 continue;
1149             }
1150             if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1151                 node.removeAttribute(a.name);
1152                 continue;
1153             }
1154             if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1155                 cleanAttr(a.name,a.value); // fixme..
1156                 continue;
1157             }
1158             if (a.name == 'style') {
1159                 cleanStyle(a.name,a.value);
1160                 continue;
1161             }
1162             /// clean up MS crap..
1163             // tecnically this should be a list of valid class'es..
1164             
1165             
1166             if (a.name == 'class') {
1167                 if (a.value.match(/^Mso/)) {
1168                     node.removeAttribute('class');
1169                 }
1170                 
1171                 if (a.value.match(/^body$/)) {
1172                     node.removeAttribute('class');
1173                 }
1174                 continue;
1175             }
1176             
1177             // style cleanup!?
1178             // class cleanup?
1179             
1180         }
1181         
1182         
1183         this.cleanUpChildren(node);
1184         
1185         
1186     },
1187     
1188     /**
1189      * Clean up MS wordisms...
1190      */
1191     cleanWord : function(node)
1192     {
1193         if (!node) {
1194             this.cleanWord(this.doc.body);
1195             return;
1196         }
1197         
1198         if(
1199                 node.nodeName == 'SPAN' &&
1200                 !node.hasAttributes() &&
1201                 node.childNodes.length == 1 &&
1202                 node.firstChild.nodeName == "#text"  
1203         ) {
1204             var textNode = node.firstChild;
1205             node.removeChild(textNode);
1206             if (node.getAttribute('lang') != 'zh-CN') {   // do not space pad on chinese characters..
1207                 node.parentNode.insertBefore(node.ownerDocument.createTextNode(" "), node);
1208             }
1209             node.parentNode.insertBefore(textNode, node);
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.removeChild(node);
1214         }
1215         
1216         if (node.nodeName == "#text") {
1217             // clean up silly Windows -- stuff?
1218             return; 
1219         }
1220         if (node.nodeName == "#comment") {
1221             node.parentNode.removeChild(node);
1222             // clean up silly Windows -- stuff?
1223             return; 
1224         }
1225         
1226         if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1227             node.parentNode.removeChild(node);
1228             return;
1229         }
1230         Roo.log(node.tagName);
1231         // remove - but keep children..
1232         if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|v:|font)/)) {
1233             Roo.log('-- removed');
1234             while (node.childNodes.length) {
1235                 var cn = node.childNodes[0];
1236                 node.removeChild(cn);
1237                 if (cn.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|v:|font)/)) {
1238                     continue;
1239                 }
1240                 node.parentNode.insertBefore(cn, node);
1241             }
1242             node.parentNode.removeChild(node);
1243             /// no need to iterate chidlren = it's got none..
1244             //this.iterateChildren(node, this.cleanWord);
1245             return;
1246         }
1247         // clean styles
1248         if (node.className.length) {
1249             
1250             var cn = node.className.split(/\W+/);
1251             var cna = [];
1252             Roo.each(cn, function(cls) {
1253                 if (cls.match(/Mso[a-zA-Z]+/)) {
1254                     return;
1255                 }
1256                 cna.push(cls);
1257             });
1258             node.className = cna.length ? cna.join(' ') : '';
1259             if (!cna.length) {
1260                 node.removeAttribute("class");
1261             }
1262         }
1263         
1264         if (node.hasAttribute("lang")) {
1265             node.removeAttribute("lang");
1266         }
1267         
1268         if (node.hasAttribute("style")) {
1269             
1270             var styles = node.getAttribute("style").split(";");
1271             var nstyle = [];
1272             Roo.each(styles, function(s) {
1273                 if (!s.match(/:/)) {
1274                     return;
1275                 }
1276                 var kv = s.split(":");
1277                 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1278                     return;
1279                 }
1280                 // what ever is left... we allow.
1281                 nstyle.push(s);
1282             });
1283             node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1284             if (!nstyle.length) {
1285                 node.removeAttribute('style');
1286             }
1287         }
1288         this.iterateChildren(node, this.cleanWord);
1289         
1290         
1291         
1292     },
1293     /**
1294      * iterateChildren of a Node, calling fn each time, using this as the scole..
1295      * @param {DomNode} node node to iterate children of.
1296      * @param {Function} fn method of this class to call on each item.
1297      */
1298     iterateChildren : function(node, fn)
1299     {
1300         if (!node.childNodes.length) {
1301                 return;
1302         }
1303         for (var i = node.childNodes.length-1; i > -1 ; i--) {
1304            fn.call(this, node.childNodes[i])
1305         }
1306     },
1307     
1308     
1309     /**
1310      * cleanTableWidths.
1311      *
1312      * Quite often pasting from word etc.. results in tables with column and widths.
1313      * This does not work well on fluid HTML layouts - like emails. - so this code should hunt an destroy them..
1314      *
1315      */
1316     cleanTableWidths : function(node)
1317     {
1318          
1319          
1320         if (!node) {
1321             this.cleanTableWidths(this.doc.body);
1322             return;
1323         }
1324         
1325         // ignore list...
1326         if (node.nodeName == "#text" || node.nodeName == "#comment") {
1327             return; 
1328         }
1329         Roo.log(node.tagName);
1330         if (!node.tagName.toLowerCase().match(/^(table|td|tr)$/)) {
1331             this.iterateChildren(node, this.cleanTableWidths);
1332             return;
1333         }
1334         if (node.hasAttribute('width')) {
1335             node.removeAttribute('width');
1336         }
1337         
1338          
1339         if (node.hasAttribute("style")) {
1340             // pretty basic...
1341             
1342             var styles = node.getAttribute("style").split(";");
1343             var nstyle = [];
1344             Roo.each(styles, function(s) {
1345                 if (!s.match(/:/)) {
1346                     return;
1347                 }
1348                 var kv = s.split(":");
1349                 if (kv[0].match(/^\s*(width|min-width)\s*$/)) {
1350                     return;
1351                 }
1352                 // what ever is left... we allow.
1353                 nstyle.push(s);
1354             });
1355             node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1356             if (!nstyle.length) {
1357                 node.removeAttribute('style');
1358             }
1359         }
1360         
1361         this.iterateChildren(node, this.cleanTableWidths);
1362         
1363         
1364     },
1365     
1366     
1367     
1368     
1369     domToHTML : function(currentElement, depth, nopadtext) {
1370         
1371         depth = depth || 0;
1372         nopadtext = nopadtext || false;
1373     
1374         if (!currentElement) {
1375             return this.domToHTML(this.doc.body);
1376         }
1377         
1378         //Roo.log(currentElement);
1379         var j;
1380         var allText = false;
1381         var nodeName = currentElement.nodeName;
1382         var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1383         
1384         if  (nodeName == '#text') {
1385             
1386             return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim();
1387         }
1388         
1389         
1390         var ret = '';
1391         if (nodeName != 'BODY') {
1392              
1393             var i = 0;
1394             // Prints the node tagName, such as <A>, <IMG>, etc
1395             if (tagName) {
1396                 var attr = [];
1397                 for(i = 0; i < currentElement.attributes.length;i++) {
1398                     // quoting?
1399                     var aname = currentElement.attributes.item(i).name;
1400                     if (!currentElement.attributes.item(i).value.length) {
1401                         continue;
1402                     }
1403                     attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1404                 }
1405                 
1406                 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1407             } 
1408             else {
1409                 
1410                 // eack
1411             }
1412         } else {
1413             tagName = false;
1414         }
1415         if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1416             return ret;
1417         }
1418         if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1419             nopadtext = true;
1420         }
1421         
1422         
1423         // Traverse the tree
1424         i = 0;
1425         var currentElementChild = currentElement.childNodes.item(i);
1426         var allText = true;
1427         var innerHTML  = '';
1428         lastnode = '';
1429         while (currentElementChild) {
1430             // Formatting code (indent the tree so it looks nice on the screen)
1431             var nopad = nopadtext;
1432             if (lastnode == 'SPAN') {
1433                 nopad  = true;
1434             }
1435             // text
1436             if  (currentElementChild.nodeName == '#text') {
1437                 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1438                 toadd = nopadtext ? toadd : toadd.trim();
1439                 if (!nopad && toadd.length > 80) {
1440                     innerHTML  += "\n" + (new Array( depth + 1 )).join( "  "  );
1441                 }
1442                 innerHTML  += toadd;
1443                 
1444                 i++;
1445                 currentElementChild = currentElement.childNodes.item(i);
1446                 lastNode = '';
1447                 continue;
1448             }
1449             allText = false;
1450             
1451             innerHTML  += nopad ? '' : "\n" + (new Array( depth + 1 )).join( "  "  );
1452                 
1453             // Recursively traverse the tree structure of the child node
1454             innerHTML   += this.domToHTML(currentElementChild, depth+1, nopadtext);
1455             lastnode = currentElementChild.nodeName;
1456             i++;
1457             currentElementChild=currentElement.childNodes.item(i);
1458         }
1459         
1460         ret += innerHTML;
1461         
1462         if (!allText) {
1463                 // The remaining code is mostly for formatting the tree
1464             ret+= nopadtext ? '' : "\n" + (new Array( depth  )).join( "  "  );
1465         }
1466         
1467         
1468         if (tagName) {
1469             ret+= "</"+tagName+">";
1470         }
1471         return ret;
1472         
1473     },
1474         
1475     applyBlacklists : function()
1476     {
1477         var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white  : [];
1478         var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black :  [];
1479         
1480         this.white = [];
1481         this.black = [];
1482         Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1483             if (b.indexOf(tag) > -1) {
1484                 return;
1485             }
1486             this.white.push(tag);
1487             
1488         }, this);
1489         
1490         Roo.each(w, function(tag) {
1491             if (b.indexOf(tag) > -1) {
1492                 return;
1493             }
1494             if (this.white.indexOf(tag) > -1) {
1495                 return;
1496             }
1497             this.white.push(tag);
1498             
1499         }, this);
1500         
1501         
1502         Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1503             if (w.indexOf(tag) > -1) {
1504                 return;
1505             }
1506             this.black.push(tag);
1507             
1508         }, this);
1509         
1510         Roo.each(b, function(tag) {
1511             if (w.indexOf(tag) > -1) {
1512                 return;
1513             }
1514             if (this.black.indexOf(tag) > -1) {
1515                 return;
1516             }
1517             this.black.push(tag);
1518             
1519         }, this);
1520         
1521         
1522         w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite  : [];
1523         b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack :  [];
1524         
1525         this.cwhite = [];
1526         this.cblack = [];
1527         Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1528             if (b.indexOf(tag) > -1) {
1529                 return;
1530             }
1531             this.cwhite.push(tag);
1532             
1533         }, this);
1534         
1535         Roo.each(w, function(tag) {
1536             if (b.indexOf(tag) > -1) {
1537                 return;
1538             }
1539             if (this.cwhite.indexOf(tag) > -1) {
1540                 return;
1541             }
1542             this.cwhite.push(tag);
1543             
1544         }, this);
1545         
1546         
1547         Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1548             if (w.indexOf(tag) > -1) {
1549                 return;
1550             }
1551             this.cblack.push(tag);
1552             
1553         }, this);
1554         
1555         Roo.each(b, function(tag) {
1556             if (w.indexOf(tag) > -1) {
1557                 return;
1558             }
1559             if (this.cblack.indexOf(tag) > -1) {
1560                 return;
1561             }
1562             this.cblack.push(tag);
1563             
1564         }, this);
1565     },
1566     
1567     setStylesheets : function(stylesheets)
1568     {
1569         if(typeof(stylesheets) == 'string'){
1570             Roo.get(this.iframe.contentDocument.head).createChild({
1571                 tag : 'link',
1572                 rel : 'stylesheet',
1573                 type : 'text/css',
1574                 href : stylesheets
1575             });
1576             
1577             return;
1578         }
1579         var _this = this;
1580      
1581         Roo.each(stylesheets, function(s) {
1582             if(!s.length){
1583                 return;
1584             }
1585             
1586             Roo.get(_this.iframe.contentDocument.head).createChild({
1587                 tag : 'link',
1588                 rel : 'stylesheet',
1589                 type : 'text/css',
1590                 href : s
1591             });
1592         });
1593
1594         
1595     },
1596     
1597     removeStylesheets : function()
1598     {
1599         var _this = this;
1600         
1601         Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1602             s.remove();
1603         });
1604     },
1605     
1606     setStyle : function(style)
1607     {
1608         Roo.get(this.iframe.contentDocument.head).createChild({
1609             tag : 'style',
1610             type : 'text/css',
1611             html : style
1612         });
1613
1614         return;
1615     }
1616     
1617     // hide stuff that is not compatible
1618     /**
1619      * @event blur
1620      * @hide
1621      */
1622     /**
1623      * @event change
1624      * @hide
1625      */
1626     /**
1627      * @event focus
1628      * @hide
1629      */
1630     /**
1631      * @event specialkey
1632      * @hide
1633      */
1634     /**
1635      * @cfg {String} fieldClass @hide
1636      */
1637     /**
1638      * @cfg {String} focusClass @hide
1639      */
1640     /**
1641      * @cfg {String} autoCreate @hide
1642      */
1643     /**
1644      * @cfg {String} inputType @hide
1645      */
1646     /**
1647      * @cfg {String} invalidClass @hide
1648      */
1649     /**
1650      * @cfg {String} invalidText @hide
1651      */
1652     /**
1653      * @cfg {String} msgFx @hide
1654      */
1655     /**
1656      * @cfg {String} validateOnBlur @hide
1657      */
1658 });
1659
1660 Roo.HtmlEditorCore.white = [
1661         'area', 'br', 'img', 'input', 'hr', 'wbr',
1662         
1663        'address', 'blockquote', 'center', 'dd',      'dir',       'div', 
1664        'dl',      'dt',         'h1',     'h2',      'h3',        'h4', 
1665        'h5',      'h6',         'hr',     'isindex', 'listing',   'marquee', 
1666        'menu',    'multicol',   'ol',     'p',       'plaintext', 'pre', 
1667        'table',   'ul',         'xmp', 
1668        
1669        'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', 
1670       'thead',   'tr', 
1671      
1672       'dir', 'menu', 'ol', 'ul', 'dl',
1673        
1674       'embed',  'object'
1675 ];
1676
1677
1678 Roo.HtmlEditorCore.black = [
1679     //    'embed',  'object', // enable - backend responsiblity to clean thiese
1680         'applet', // 
1681         'base',   'basefont', 'bgsound', 'blink',  'body', 
1682         'frame',  'frameset', 'head',    'html',   'ilayer', 
1683         'iframe', 'layer',  'link',     'meta',    'object',   
1684         'script', 'style' ,'title',  'xml' // clean later..
1685 ];
1686 Roo.HtmlEditorCore.clean = [
1687     'script', 'style', 'title', 'xml'
1688 ];
1689 Roo.HtmlEditorCore.remove = [
1690     'font'
1691 ];
1692 // attributes..
1693
1694 Roo.HtmlEditorCore.ablack = [
1695     'on'
1696 ];
1697     
1698 Roo.HtmlEditorCore.aclean = [ 
1699     'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc' 
1700 ];
1701
1702 // protocols..
1703 Roo.HtmlEditorCore.pwhite= [
1704         'http',  'https',  'mailto'
1705 ];
1706
1707 // white listed style attributes.
1708 Roo.HtmlEditorCore.cwhite= [
1709       //  'text-align', /// default is to allow most things..
1710       
1711          
1712 //        'font-size'//??
1713 ];
1714
1715 // black listed style attributes.
1716 Roo.HtmlEditorCore.cblack= [
1717       //  'font-size' -- this can be set by the project 
1718 ];
1719
1720
1721 Roo.HtmlEditorCore.swapCodes   =[ 
1722     [    8211, "--" ], 
1723     [    8212, "--" ], 
1724     [    8216,  "'" ],  
1725     [    8217, "'" ],  
1726     [    8220, '"' ],  
1727     [    8221, '"' ],  
1728     [    8226, "*" ],  
1729     [    8230, "..." ]
1730 ]; 
1731
1732