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