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             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         
174         return '<html><head>' + st  +
175             //<style type="text/css">' +
176             //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
177             //'</style>' +
178             ' </head><body class="roo-htmleditor-body"></body></html>';
179     },
180
181     // private
182     onRender : function(ct, position)
183     {
184         var _t = this;
185         //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
186         this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
187         
188         
189         this.el.dom.style.border = '0 none';
190         this.el.dom.setAttribute('tabIndex', -1);
191         this.el.addClass('x-hidden hide');
192         
193         
194         
195         if(Roo.isIE){ // fix IE 1px bogus margin
196             this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
197         }
198        
199         
200         this.frameId = Roo.id();
201         
202          
203         
204         var iframe = this.owner.wrap.createChild({
205             tag: 'iframe',
206             cls: 'form-control', // bootstrap..
207             id: this.frameId,
208             name: this.frameId,
209             frameBorder : 'no',
210             'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL  :  "javascript:false"
211         }, this.el
212         );
213         
214         
215         this.iframe = iframe.dom;
216
217          this.assignDocWin();
218         
219         this.doc.designMode = 'on';
220        
221         this.doc.open();
222         this.doc.write(this.getDocMarkup());
223         this.doc.close();
224
225         
226         var task = { // must defer to wait for browser to be ready
227             run : function(){
228                 //console.log("run task?" + this.doc.readyState);
229                 this.assignDocWin();
230                 if(this.doc.body || this.doc.readyState == 'complete'){
231                     try {
232                         this.doc.designMode="on";
233                     } catch (e) {
234                         return;
235                     }
236                     Roo.TaskMgr.stop(task);
237                     this.initEditor.defer(10, this);
238                 }
239             },
240             interval : 10,
241             duration: 10000,
242             scope: this
243         };
244         Roo.TaskMgr.start(task);
245
246     },
247
248     // private
249     onResize : function(w, h)
250     {
251          Roo.log('resize: ' +w + ',' + h );
252         //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
253         if(!this.iframe){
254             return;
255         }
256         if(typeof w == 'number'){
257             
258             this.iframe.style.width = w + 'px';
259         }
260         if(typeof h == 'number'){
261             
262             this.iframe.style.height = h + 'px';
263             if(this.doc){
264                 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
265             }
266         }
267         
268     },
269
270     /**
271      * Toggles the editor between standard and source edit mode.
272      * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
273      */
274     toggleSourceEdit : function(sourceEditMode){
275         
276         this.sourceEditMode = sourceEditMode === true;
277         
278         if(this.sourceEditMode){
279  
280             Roo.get(this.iframe).addClass(['x-hidden','hide']);     //FIXME - what's the BS styles for these
281             
282         }else{
283             Roo.get(this.iframe).removeClass(['x-hidden','hide']);
284             //this.iframe.className = '';
285             this.deferFocus();
286         }
287         //this.setSize(this.owner.wrap.getSize());
288         //this.fireEvent('editmodechange', this, this.sourceEditMode);
289     },
290
291     
292   
293
294     /**
295      * Protected method that will not generally be called directly. If you need/want
296      * custom HTML cleanup, this is the method you should override.
297      * @param {String} html The HTML to be cleaned
298      * return {String} The cleaned HTML
299      */
300     cleanHtml : function(html){
301         html = String(html);
302         if(html.length > 5){
303             if(Roo.isSafari){ // strip safari nonsense
304                 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
305             }
306         }
307         if(html == '&nbsp;'){
308             html = '';
309         }
310         return html;
311     },
312
313     /**
314      * HTML Editor -> Textarea
315      * Protected method that will not generally be called directly. Syncs the contents
316      * of the editor iframe with the textarea.
317      */
318     syncValue : function(){
319         if(this.initialized){
320             var bd = (this.doc.body || this.doc.documentElement);
321             //this.cleanUpPaste(); -- this is done else where and causes havoc..
322             var html = bd.innerHTML;
323             if(Roo.isSafari){
324                 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
325                 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
326                 if(m && m[1]){
327                     html = '<div style="'+m[0]+'">' + html + '</div>';
328                 }
329             }
330             html = this.cleanHtml(html);
331             // fix up the special chars.. normaly like back quotes in word...
332             // however we do not want to do this with chinese..
333             html = html.replace(/([\x80-\uffff])/g, function (a, b) {
334                 var cc = b.charCodeAt();
335                 if (
336                     (cc >= 0x4E00 && cc < 0xA000 ) ||
337                     (cc >= 0x3400 && cc < 0x4E00 ) ||
338                     (cc >= 0xf900 && cc < 0xfb00 )
339                 ) {
340                         return b;
341                 }
342                 return "&#"+cc+";" 
343             });
344             if(this.owner.fireEvent('beforesync', this, html) !== false){
345                 this.el.dom.value = html;
346                 this.owner.fireEvent('sync', this, html);
347             }
348         }
349     },
350
351     /**
352      * Protected method that will not generally be called directly. Pushes the value of the textarea
353      * into the iframe editor.
354      */
355     pushValue : function(){
356         if(this.initialized){
357             var v = this.el.dom.value.trim();
358             
359 //            if(v.length < 1){
360 //                v = '&#160;';
361 //            }
362             
363             if(this.owner.fireEvent('beforepush', this, v) !== false){
364                 var d = (this.doc.body || this.doc.documentElement);
365                 d.innerHTML = v;
366                 this.cleanUpPaste();
367                 this.el.dom.value = d.innerHTML;
368                 this.owner.fireEvent('push', this, v);
369             }
370         }
371     },
372
373     // private
374     deferFocus : function(){
375         this.focus.defer(10, this);
376     },
377
378     // doc'ed in Field
379     focus : function(){
380         if(this.win && !this.sourceEditMode){
381             this.win.focus();
382         }else{
383             this.el.focus();
384         }
385     },
386     
387     assignDocWin: function()
388     {
389         var iframe = this.iframe;
390         
391          if(Roo.isIE){
392             this.doc = iframe.contentWindow.document;
393             this.win = iframe.contentWindow;
394         } else {
395 //            if (!Roo.get(this.frameId)) {
396 //                return;
397 //            }
398 //            this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
399 //            this.win = Roo.get(this.frameId).dom.contentWindow;
400             
401             if (!Roo.get(this.frameId) && !iframe.contentDocument) {
402                 return;
403             }
404             
405             this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
406             this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
407         }
408     },
409     
410     // private
411     initEditor : function(){
412         //console.log("INIT EDITOR");
413         this.assignDocWin();
414         
415         
416         
417         this.doc.designMode="on";
418         this.doc.open();
419         this.doc.write(this.getDocMarkup());
420         this.doc.close();
421         
422         var dbody = (this.doc.body || this.doc.documentElement);
423         //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
424         // this copies styles from the containing element into thsi one..
425         // not sure why we need all of this..
426         //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
427         
428         //var ss = this.el.getStyles( 'background-image', 'background-repeat');
429         //ss['background-attachment'] = 'fixed'; // w3c
430         dbody.bgProperties = 'fixed'; // ie
431         //Roo.DomHelper.applyStyles(dbody, ss);
432         Roo.EventManager.on(this.doc, {
433             //'mousedown': this.onEditorEvent,
434             'mouseup': this.onEditorEvent,
435             'dblclick': this.onEditorEvent,
436             'click': this.onEditorEvent,
437             'keyup': this.onEditorEvent,
438             buffer:100,
439             scope: this
440         });
441         if(Roo.isGecko){
442             Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
443         }
444         if(Roo.isIE || Roo.isSafari || Roo.isOpera){
445             Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
446         }
447         this.initialized = true;
448
449         this.owner.fireEvent('initialize', this);
450         this.pushValue();
451     },
452
453     // private
454     onDestroy : function(){
455         
456         
457         
458         if(this.rendered){
459             
460             //for (var i =0; i < this.toolbars.length;i++) {
461             //    // fixme - ask toolbars for heights?
462             //    this.toolbars[i].onDestroy();
463            // }
464             
465             //this.wrap.dom.innerHTML = '';
466             //this.wrap.remove();
467         }
468     },
469
470     // private
471     onFirstFocus : function(){
472         
473         this.assignDocWin();
474         
475         
476         this.activated = true;
477          
478     
479         if(Roo.isGecko){ // prevent silly gecko errors
480             this.win.focus();
481             var s = this.win.getSelection();
482             if(!s.focusNode || s.focusNode.nodeType != 3){
483                 var r = s.getRangeAt(0);
484                 r.selectNodeContents((this.doc.body || this.doc.documentElement));
485                 r.collapse(true);
486                 this.deferFocus();
487             }
488             try{
489                 this.execCmd('useCSS', true);
490                 this.execCmd('styleWithCSS', false);
491             }catch(e){}
492         }
493         this.owner.fireEvent('activate', this);
494     },
495
496     // private
497     adjustFont: function(btn){
498         var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
499         //if(Roo.isSafari){ // safari
500         //    adjust *= 2;
501        // }
502         var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
503         if(Roo.isSafari){ // safari
504             var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
505             v =  (v < 10) ? 10 : v;
506             v =  (v > 48) ? 48 : v;
507             v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
508             
509         }
510         
511         
512         v = Math.max(1, v+adjust);
513         
514         this.execCmd('FontSize', v  );
515     },
516
517     onEditorEvent : function(e)
518     {
519         this.owner.fireEvent('editorevent', this, e);
520       //  this.updateToolbar();
521         this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
522     },
523
524     insertTag : function(tg)
525     {
526         // could be a bit smarter... -> wrap the current selected tRoo..
527         if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
528             
529             range = this.createRange(this.getSelection());
530             var wrappingNode = this.doc.createElement(tg.toLowerCase());
531             wrappingNode.appendChild(range.extractContents());
532             range.insertNode(wrappingNode);
533
534             return;
535             
536             
537             
538         }
539         this.execCmd("formatblock",   tg);
540         
541     },
542     
543     insertText : function(txt)
544     {
545         
546         
547         var range = this.createRange();
548         range.deleteContents();
549                //alert(Sender.getAttribute('label'));
550                
551         range.insertNode(this.doc.createTextNode(txt));
552     } ,
553     
554      
555
556     /**
557      * Executes a Midas editor command on the editor document and performs necessary focus and
558      * toolbar updates. <b>This should only be called after the editor is initialized.</b>
559      * @param {String} cmd The Midas command
560      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
561      */
562     relayCmd : function(cmd, value){
563         this.win.focus();
564         this.execCmd(cmd, value);
565         this.owner.fireEvent('editorevent', this);
566         //this.updateToolbar();
567         this.owner.deferFocus();
568     },
569
570     /**
571      * Executes a Midas editor command directly on the editor document.
572      * For visual commands, you should use {@link #relayCmd} instead.
573      * <b>This should only be called after the editor is initialized.</b>
574      * @param {String} cmd The Midas command
575      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
576      */
577     execCmd : function(cmd, value){
578         this.doc.execCommand(cmd, false, value === undefined ? null : value);
579         this.syncValue();
580     },
581  
582  
583    
584     /**
585      * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
586      * to insert tRoo.
587      * @param {String} text | dom node.. 
588      */
589     insertAtCursor : function(text)
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         }
945         if (!nodeIsBefore && nodeIsAfter) {
946             return 1; //right trailed.
947         }
948         
949         if (nodeIsBefore && !nodeIsAfter) {
950             return 2;  // left trailed.
951         }
952         // fully contined.
953         return 3;
954     },
955
956     // private? - in a new class?
957     cleanUpPaste :  function()
958     {
959         // cleans up the whole document..
960         Roo.log('cleanuppaste');
961         
962         this.cleanUpChildren(this.doc.body);
963         var clean = this.cleanWordChars(this.doc.body.innerHTML);
964         if (clean != this.doc.body.innerHTML) {
965             this.doc.body.innerHTML = clean;
966         }
967         
968     },
969     
970     cleanWordChars : function(input) {// change the chars to hex code
971         var he = Roo.HtmlEditorCore;
972         
973         var output = input;
974         Roo.each(he.swapCodes, function(sw) { 
975             var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
976             
977             output = output.replace(swapper, sw[1]);
978         });
979         
980         return output;
981     },
982     
983     
984     cleanUpChildren : function (n)
985     {
986         if (!n.childNodes.length) {
987             return;
988         }
989         for (var i = n.childNodes.length-1; i > -1 ; i--) {
990            this.cleanUpChild(n.childNodes[i]);
991         }
992     },
993     
994     
995         
996     
997     cleanUpChild : function (node)
998     {
999         var ed = this;
1000         //console.log(node);
1001         if (node.nodeName == "#text") {
1002             // clean up silly Windows -- stuff?
1003             return; 
1004         }
1005         if (node.nodeName == "#comment") {
1006             node.parentNode.removeChild(node);
1007             // clean up silly Windows -- stuff?
1008             return; 
1009         }
1010         var lcname = node.tagName.toLowerCase();
1011         // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full
1012         // whitelist of tags..
1013         
1014         if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
1015             // remove node.
1016             node.parentNode.removeChild(node);
1017             return;
1018             
1019         }
1020         
1021         var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1022         
1023         // remove <a name=....> as rendering on yahoo mailer is borked with this.
1024         // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1025         
1026         //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1027         //    remove_keep_children = true;
1028         //}
1029         
1030         if (remove_keep_children) {
1031             this.cleanUpChildren(node);
1032             // inserts everything just before this node...
1033             while (node.childNodes.length) {
1034                 var cn = node.childNodes[0];
1035                 node.removeChild(cn);
1036                 node.parentNode.insertBefore(cn, node);
1037             }
1038             node.parentNode.removeChild(node);
1039             return;
1040         }
1041         
1042         if (!node.attributes || !node.attributes.length) {
1043             this.cleanUpChildren(node);
1044             return;
1045         }
1046         
1047         function cleanAttr(n,v)
1048         {
1049             
1050             if (v.match(/^\./) || v.match(/^\//)) {
1051                 return;
1052             }
1053             if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1054                 return;
1055             }
1056             if (v.match(/^#/)) {
1057                 return;
1058             }
1059 //            Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1060             node.removeAttribute(n);
1061             
1062         }
1063         
1064         var cwhite = this.cwhite;
1065         var cblack = this.cblack;
1066             
1067         function cleanStyle(n,v)
1068         {
1069             if (v.match(/expression/)) { //XSS?? should we even bother..
1070                 node.removeAttribute(n);
1071                 return;
1072             }
1073             
1074             var parts = v.split(/;/);
1075             var clean = [];
1076             
1077             Roo.each(parts, function(p) {
1078                 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1079                 if (!p.length) {
1080                     return true;
1081                 }
1082                 var l = p.split(':').shift().replace(/\s+/g,'');
1083                 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1084                 
1085                 if ( cwhite.length && cblack.indexOf(l) > -1) {
1086 //                    Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1087                     //node.removeAttribute(n);
1088                     return true;
1089                 }
1090                 //Roo.log()
1091                 // only allow 'c whitelisted system attributes'
1092                 if ( cwhite.length &&  cwhite.indexOf(l) < 0) {
1093 //                    Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1094                     //node.removeAttribute(n);
1095                     return true;
1096                 }
1097                 
1098                 
1099                  
1100                 
1101                 clean.push(p);
1102                 return true;
1103             });
1104             if (clean.length) { 
1105                 node.setAttribute(n, clean.join(';'));
1106             } else {
1107                 node.removeAttribute(n);
1108             }
1109             
1110         }
1111         
1112         
1113         for (var i = node.attributes.length-1; i > -1 ; i--) {
1114             var a = node.attributes[i];
1115             //console.log(a);
1116             
1117             if (a.name.toLowerCase().substr(0,2)=='on')  {
1118                 node.removeAttribute(a.name);
1119                 continue;
1120             }
1121             if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1122                 node.removeAttribute(a.name);
1123                 continue;
1124             }
1125             if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1126                 cleanAttr(a.name,a.value); // fixme..
1127                 continue;
1128             }
1129             if (a.name == 'style') {
1130                 cleanStyle(a.name,a.value);
1131                 continue;
1132             }
1133             /// clean up MS crap..
1134             // tecnically this should be a list of valid class'es..
1135             
1136             
1137             if (a.name == 'class') {
1138                 if (a.value.match(/^Mso/)) {
1139                     node.className = '';
1140                 }
1141                 
1142                 if (a.value.match(/^body$/)) {
1143                     node.className = '';
1144                 }
1145                 continue;
1146             }
1147             
1148             // style cleanup!?
1149             // class cleanup?
1150             
1151         }
1152         
1153         
1154         this.cleanUpChildren(node);
1155         
1156         
1157     },
1158     
1159     /**
1160      * Clean up MS wordisms...
1161      */
1162     cleanWord : function(node)
1163     {
1164         
1165         
1166         if (!node) {
1167             this.cleanWord(this.doc.body);
1168             return;
1169         }
1170         if (node.nodeName == "#text") {
1171             // clean up silly Windows -- stuff?
1172             return; 
1173         }
1174         if (node.nodeName == "#comment") {
1175             node.parentNode.removeChild(node);
1176             // clean up silly Windows -- stuff?
1177             return; 
1178         }
1179         
1180         if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1181             node.parentNode.removeChild(node);
1182             return;
1183         }
1184         
1185         // remove - but keep children..
1186         if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1187             while (node.childNodes.length) {
1188                 var cn = node.childNodes[0];
1189                 node.removeChild(cn);
1190                 node.parentNode.insertBefore(cn, node);
1191             }
1192             node.parentNode.removeChild(node);
1193             this.iterateChildren(node, this.cleanWord);
1194             return;
1195         }
1196         // clean styles
1197         if (node.className.length) {
1198             
1199             var cn = node.className.split(/\W+/);
1200             var cna = [];
1201             Roo.each(cn, function(cls) {
1202                 if (cls.match(/Mso[a-zA-Z]+/)) {
1203                     return;
1204                 }
1205                 cna.push(cls);
1206             });
1207             node.className = cna.length ? cna.join(' ') : '';
1208             if (!cna.length) {
1209                 node.removeAttribute("class");
1210             }
1211         }
1212         
1213         if (node.hasAttribute("lang")) {
1214             node.removeAttribute("lang");
1215         }
1216         
1217         if (node.hasAttribute("style")) {
1218             
1219             var styles = node.getAttribute("style").split(";");
1220             var nstyle = [];
1221             Roo.each(styles, function(s) {
1222                 if (!s.match(/:/)) {
1223                     return;
1224                 }
1225                 var kv = s.split(":");
1226                 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1227                     return;
1228                 }
1229                 // what ever is left... we allow.
1230                 nstyle.push(s);
1231             });
1232             node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1233             if (!nstyle.length) {
1234                 node.removeAttribute('style');
1235             }
1236         }
1237         this.iterateChildren(node, this.cleanWord);
1238         
1239         
1240         
1241     },
1242     /**
1243      * iterateChildren of a Node, calling fn each time, using this as the scole..
1244      * @param {DomNode} node node to iterate children of.
1245      * @param {Function} fn method of this class to call on each item.
1246      */
1247     iterateChildren : function(node, fn)
1248     {
1249         if (!node.childNodes.length) {
1250                 return;
1251         }
1252         for (var i = node.childNodes.length-1; i > -1 ; i--) {
1253            fn.call(this, node.childNodes[i])
1254         }
1255     },
1256     
1257     
1258     /**
1259      * cleanTableWidths.
1260      *
1261      * Quite often pasting from word etc.. results in tables with column and widths.
1262      * This does not work well on fluid HTML layouts - like emails. - so this code should hunt an destroy them..
1263      *
1264      */
1265     cleanTableWidths : function(node)
1266     {
1267          
1268          
1269         if (!node) {
1270             this.cleanTableWidths(this.doc.body);
1271             return;
1272         }
1273         
1274         // ignore list...
1275         if (node.nodeName == "#text" || node.nodeName == "#comment") {
1276             return; 
1277         }
1278         Roo.log(node.tagName);
1279         if (!node.tagName.toLowerCase().match(/^(table|td|tr)$/)) {
1280             this.iterateChildren(node, this.cleanTableWidths);
1281             return;
1282         }
1283         if (node.hasAttribute('width')) {
1284             node.removeAttribute('width');
1285         }
1286         
1287          
1288         if (node.hasAttribute("style")) {
1289             // pretty basic...
1290             
1291             var styles = node.getAttribute("style").split(";");
1292             var nstyle = [];
1293             Roo.each(styles, function(s) {
1294                 if (!s.match(/:/)) {
1295                     return;
1296                 }
1297                 var kv = s.split(":");
1298                 if (kv[0].match(/^\s*(width|min-width)\s*$/)) {
1299                     return;
1300                 }
1301                 // what ever is left... we allow.
1302                 nstyle.push(s);
1303             });
1304             node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1305             if (!nstyle.length) {
1306                 node.removeAttribute('style');
1307             }
1308         }
1309         
1310         this.iterateChildren(node, this.cleanTableWidths);
1311         
1312         
1313     },
1314     
1315     
1316     
1317     
1318     domToHTML : function(currentElement, depth, nopadtext) {
1319         
1320         depth = depth || 0;
1321         nopadtext = nopadtext || false;
1322     
1323         if (!currentElement) {
1324             return this.domToHTML(this.doc.body);
1325         }
1326         
1327         //Roo.log(currentElement);
1328         var j;
1329         var allText = false;
1330         var nodeName = currentElement.nodeName;
1331         var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1332         
1333         if  (nodeName == '#text') {
1334             
1335             return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim();
1336         }
1337         
1338         
1339         var ret = '';
1340         if (nodeName != 'BODY') {
1341              
1342             var i = 0;
1343             // Prints the node tagName, such as <A>, <IMG>, etc
1344             if (tagName) {
1345                 var attr = [];
1346                 for(i = 0; i < currentElement.attributes.length;i++) {
1347                     // quoting?
1348                     var aname = currentElement.attributes.item(i).name;
1349                     if (!currentElement.attributes.item(i).value.length) {
1350                         continue;
1351                     }
1352                     attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1353                 }
1354                 
1355                 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1356             } 
1357             else {
1358                 
1359                 // eack
1360             }
1361         } else {
1362             tagName = false;
1363         }
1364         if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1365             return ret;
1366         }
1367         if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1368             nopadtext = true;
1369         }
1370         
1371         
1372         // Traverse the tree
1373         i = 0;
1374         var currentElementChild = currentElement.childNodes.item(i);
1375         var allText = true;
1376         var innerHTML  = '';
1377         lastnode = '';
1378         while (currentElementChild) {
1379             // Formatting code (indent the tree so it looks nice on the screen)
1380             var nopad = nopadtext;
1381             if (lastnode == 'SPAN') {
1382                 nopad  = true;
1383             }
1384             // text
1385             if  (currentElementChild.nodeName == '#text') {
1386                 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1387                 toadd = nopadtext ? toadd : toadd.trim();
1388                 if (!nopad && toadd.length > 80) {
1389                     innerHTML  += "\n" + (new Array( depth + 1 )).join( "  "  );
1390                 }
1391                 innerHTML  += toadd;
1392                 
1393                 i++;
1394                 currentElementChild = currentElement.childNodes.item(i);
1395                 lastNode = '';
1396                 continue;
1397             }
1398             allText = false;
1399             
1400             innerHTML  += nopad ? '' : "\n" + (new Array( depth + 1 )).join( "  "  );
1401                 
1402             // Recursively traverse the tree structure of the child node
1403             innerHTML   += this.domToHTML(currentElementChild, depth+1, nopadtext);
1404             lastnode = currentElementChild.nodeName;
1405             i++;
1406             currentElementChild=currentElement.childNodes.item(i);
1407         }
1408         
1409         ret += innerHTML;
1410         
1411         if (!allText) {
1412                 // The remaining code is mostly for formatting the tree
1413             ret+= nopadtext ? '' : "\n" + (new Array( depth  )).join( "  "  );
1414         }
1415         
1416         
1417         if (tagName) {
1418             ret+= "</"+tagName+">";
1419         }
1420         return ret;
1421         
1422     },
1423         
1424     applyBlacklists : function()
1425     {
1426         var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white  : [];
1427         var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black :  [];
1428         
1429         this.white = [];
1430         this.black = [];
1431         Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1432             if (b.indexOf(tag) > -1) {
1433                 return;
1434             }
1435             this.white.push(tag);
1436             
1437         }, this);
1438         
1439         Roo.each(w, function(tag) {
1440             if (b.indexOf(tag) > -1) {
1441                 return;
1442             }
1443             if (this.white.indexOf(tag) > -1) {
1444                 return;
1445             }
1446             this.white.push(tag);
1447             
1448         }, this);
1449         
1450         
1451         Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1452             if (w.indexOf(tag) > -1) {
1453                 return;
1454             }
1455             this.black.push(tag);
1456             
1457         }, this);
1458         
1459         Roo.each(b, function(tag) {
1460             if (w.indexOf(tag) > -1) {
1461                 return;
1462             }
1463             if (this.black.indexOf(tag) > -1) {
1464                 return;
1465             }
1466             this.black.push(tag);
1467             
1468         }, this);
1469         
1470         
1471         w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite  : [];
1472         b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack :  [];
1473         
1474         this.cwhite = [];
1475         this.cblack = [];
1476         Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1477             if (b.indexOf(tag) > -1) {
1478                 return;
1479             }
1480             this.cwhite.push(tag);
1481             
1482         }, this);
1483         
1484         Roo.each(w, function(tag) {
1485             if (b.indexOf(tag) > -1) {
1486                 return;
1487             }
1488             if (this.cwhite.indexOf(tag) > -1) {
1489                 return;
1490             }
1491             this.cwhite.push(tag);
1492             
1493         }, this);
1494         
1495         
1496         Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1497             if (w.indexOf(tag) > -1) {
1498                 return;
1499             }
1500             this.cblack.push(tag);
1501             
1502         }, this);
1503         
1504         Roo.each(b, function(tag) {
1505             if (w.indexOf(tag) > -1) {
1506                 return;
1507             }
1508             if (this.cblack.indexOf(tag) > -1) {
1509                 return;
1510             }
1511             this.cblack.push(tag);
1512             
1513         }, this);
1514     },
1515     
1516     setStylesheets : function(stylesheets)
1517     {
1518         if(typeof(stylesheets) == 'string'){
1519             Roo.get(this.iframe.contentDocument.head).createChild({
1520                 tag : 'link',
1521                 rel : 'stylesheet',
1522                 type : 'text/css',
1523                 href : stylesheets
1524             });
1525             
1526             return;
1527         }
1528         var _this = this;
1529      
1530         Roo.each(stylesheets, function(s) {
1531             if(!s.length){
1532                 return;
1533             }
1534             
1535             Roo.get(_this.iframe.contentDocument.head).createChild({
1536                 tag : 'link',
1537                 rel : 'stylesheet',
1538                 type : 'text/css',
1539                 href : s
1540             });
1541         });
1542
1543         
1544     },
1545     
1546     removeStylesheets : function()
1547     {
1548         var _this = this;
1549         
1550         Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1551             s.remove();
1552         });
1553     },
1554     
1555     setStyle : function(style)
1556     {
1557         Roo.get(this.iframe.contentDocument.head).createChild({
1558             tag : 'style',
1559             type : 'text/css',
1560             html : style
1561         });
1562
1563         return;
1564     }
1565     
1566     // hide stuff that is not compatible
1567     /**
1568      * @event blur
1569      * @hide
1570      */
1571     /**
1572      * @event change
1573      * @hide
1574      */
1575     /**
1576      * @event focus
1577      * @hide
1578      */
1579     /**
1580      * @event specialkey
1581      * @hide
1582      */
1583     /**
1584      * @cfg {String} fieldClass @hide
1585      */
1586     /**
1587      * @cfg {String} focusClass @hide
1588      */
1589     /**
1590      * @cfg {String} autoCreate @hide
1591      */
1592     /**
1593      * @cfg {String} inputType @hide
1594      */
1595     /**
1596      * @cfg {String} invalidClass @hide
1597      */
1598     /**
1599      * @cfg {String} invalidText @hide
1600      */
1601     /**
1602      * @cfg {String} msgFx @hide
1603      */
1604     /**
1605      * @cfg {String} validateOnBlur @hide
1606      */
1607 });
1608
1609 Roo.HtmlEditorCore.white = [
1610         'area', 'br', 'img', 'input', 'hr', 'wbr',
1611         
1612        'address', 'blockquote', 'center', 'dd',      'dir',       'div', 
1613        'dl',      'dt',         'h1',     'h2',      'h3',        'h4', 
1614        'h5',      'h6',         'hr',     'isindex', 'listing',   'marquee', 
1615        'menu',    'multicol',   'ol',     'p',       'plaintext', 'pre', 
1616        'table',   'ul',         'xmp', 
1617        
1618        'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', 
1619       'thead',   'tr', 
1620      
1621       'dir', 'menu', 'ol', 'ul', 'dl',
1622        
1623       'embed',  'object'
1624 ];
1625
1626
1627 Roo.HtmlEditorCore.black = [
1628     //    'embed',  'object', // enable - backend responsiblity to clean thiese
1629         'applet', // 
1630         'base',   'basefont', 'bgsound', 'blink',  'body', 
1631         'frame',  'frameset', 'head',    'html',   'ilayer', 
1632         'iframe', 'layer',  'link',     'meta',    'object',   
1633         'script', 'style' ,'title',  'xml' // clean later..
1634 ];
1635 Roo.HtmlEditorCore.clean = [
1636     'script', 'style', 'title', 'xml'
1637 ];
1638 Roo.HtmlEditorCore.remove = [
1639     'font'
1640 ];
1641 // attributes..
1642
1643 Roo.HtmlEditorCore.ablack = [
1644     'on'
1645 ];
1646     
1647 Roo.HtmlEditorCore.aclean = [ 
1648     'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc' 
1649 ];
1650
1651 // protocols..
1652 Roo.HtmlEditorCore.pwhite= [
1653         'http',  'https',  'mailto'
1654 ];
1655
1656 // white listed style attributes.
1657 Roo.HtmlEditorCore.cwhite= [
1658       //  'text-align', /// default is to allow most things..
1659       
1660          
1661 //        'font-size'//??
1662 ];
1663
1664 // black listed style attributes.
1665 Roo.HtmlEditorCore.cblack= [
1666       //  'font-size' -- this can be set by the project 
1667 ];
1668
1669
1670 Roo.HtmlEditorCore.swapCodes   =[ 
1671     [    8211, "--" ], 
1672     [    8212, "--" ], 
1673     [    8216,  "'" ],  
1674     [    8217, "'" ],  
1675     [    8220, '"' ],  
1676     [    8221, '"' ],  
1677     [    8226, "*" ],  
1678     [    8230, "..." ]
1679 ]; 
1680
1681