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