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