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