Changed Roo/HtmlEditorCore.jsRoo/htmleditor/BlockFigure.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     
79     // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
80     
81     // defaults : white / black...
82     this.applyBlacklists();
83     
84     
85     
86 };
87
88
89 Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
90
91
92      /**
93      * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field 
94      */
95     
96     owner : false,
97     
98      /**
99      * @cfg {String} resizable  's' or 'se' or 'e' - wrapps the element in a
100      *                        Roo.resizable.
101      */
102     resizable : false,
103      /**
104      * @cfg {Number} height (in pixels)
105      */   
106     height: 300,
107    /**
108      * @cfg {Number} width (in pixels)
109      */   
110     width: 500,
111      /**
112      * @cfg {boolean} autoClean - default true - loading and saving will remove quite a bit of formating,
113      *         if you are doing an email editor, this probably needs disabling, it's designed
114      */
115     autoClean: true,
116     
117     /**
118      * @cfg {boolean} enableBlocks - default true - if the block editor (table and figure should be enabled)
119      */
120     enableBlocks : true,
121     /**
122      * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
123      * 
124      */
125     stylesheets: false,
126      /**
127      * @cfg {String} language default en - language of text (usefull for rtl languages)
128      * 
129      */
130     language: 'en',
131     
132     /**
133      * @cfg {boolean} allowComments - default false - allow comments in HTML source
134      *          - by default they are stripped - if you are editing email you may need this.
135      */
136     allowComments: false,
137     // id of frame..
138     frameId: false,
139     
140     // private properties
141     validationEvent : false,
142     deferHeight: true,
143     initialized : false,
144     activated : false,
145     sourceEditMode : false,
146     onFocus : Roo.emptyFn,
147     iframePad:3,
148     hideMode:'offsets',
149     
150     clearUp: true,
151     
152     // blacklist + whitelisted elements..
153     black: false,
154     white: false,
155      
156     bodyCls : '',
157
158     
159     undoManager : false,
160     /**
161      * Protected method that will not generally be called directly. It
162      * is called when the editor initializes the iframe with HTML contents. Override this method if you
163      * want to change the initialization markup of the iframe (e.g. to add stylesheets).
164      */
165     getDocMarkup : function(){
166         // body styles..
167         var st = '';
168         
169         // inherit styels from page...?? 
170         if (this.stylesheets === false) {
171             
172             Roo.get(document.head).select('style').each(function(node) {
173                 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
174             });
175             
176             Roo.get(document.head).select('link').each(function(node) { 
177                 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
178             });
179             
180         } else if (!this.stylesheets.length) {
181                 // simple..
182                 st = '<style type="text/css">' +
183                     'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
184                    '</style>';
185         } else {
186             for (var i in this.stylesheets) {
187                 if (typeof(this.stylesheets[i]) != 'string') {
188                     continue;
189                 }
190                 st += '<link rel="stylesheet" href="' + this.stylesheets[i] +'" type="text/css">';
191             }
192             
193         }
194         
195         st +=  '<style type="text/css">' +
196             'IMG { cursor: pointer } ' +
197         '</style>';
198         
199         st += '<meta name="google" content="notranslate">';
200         
201         var cls = 'notranslate roo-htmleditor-body';
202         
203         if(this.bodyCls.length){
204             cls += ' ' + this.bodyCls;
205         }
206         
207         return '<html  class="notranslate" translate="no"><head>' + st  +
208             //<style type="text/css">' +
209             //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
210             //'</style>' +
211             ' </head><body contenteditable="true" data-enable-grammerly="true" class="' +  cls + '"></body></html>';
212     },
213
214     // private
215     onRender : function(ct, position)
216     {
217         var _t = this;
218         //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
219         this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
220         
221         
222         this.el.dom.style.border = '0 none';
223         this.el.dom.setAttribute('tabIndex', -1);
224         this.el.addClass('x-hidden hide');
225         
226         
227         
228         if(Roo.isIE){ // fix IE 1px bogus margin
229             this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
230         }
231        
232         
233         this.frameId = Roo.id();
234         
235          
236         
237         var iframe = this.owner.wrap.createChild({
238             tag: 'iframe',
239             cls: 'form-control', // bootstrap..
240             id: this.frameId,
241             name: this.frameId,
242             frameBorder : 'no',
243             'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL  :  "javascript:false"
244         }, this.el
245         );
246         
247         
248         this.iframe = iframe.dom;
249
250         this.assignDocWin();
251         
252         this.doc.designMode = 'on';
253        
254         this.doc.open();
255         this.doc.write(this.getDocMarkup());
256         this.doc.close();
257
258         
259         var task = { // must defer to wait for browser to be ready
260             run : function(){
261                 //console.log("run task?" + this.doc.readyState);
262                 this.assignDocWin();
263                 if(this.doc.body || this.doc.readyState == 'complete'){
264                     try {
265                         this.doc.designMode="on";
266                         
267                     } catch (e) {
268                         return;
269                     }
270                     Roo.TaskMgr.stop(task);
271                     this.initEditor.defer(10, this);
272                 }
273             },
274             interval : 10,
275             duration: 10000,
276             scope: this
277         };
278         Roo.TaskMgr.start(task);
279
280     },
281
282     // private
283     onResize : function(w, h)
284     {
285          Roo.log('resize: ' +w + ',' + h );
286         //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
287         if(!this.iframe){
288             return;
289         }
290         if(typeof w == 'number'){
291             
292             this.iframe.style.width = w + 'px';
293         }
294         if(typeof h == 'number'){
295             
296             this.iframe.style.height = h + 'px';
297             if(this.doc){
298                 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
299             }
300         }
301         
302     },
303
304     /**
305      * Toggles the editor between standard and source edit mode.
306      * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
307      */
308     toggleSourceEdit : function(sourceEditMode){
309         
310         this.sourceEditMode = sourceEditMode === true;
311         
312         if(this.sourceEditMode){
313  
314             Roo.get(this.iframe).addClass(['x-hidden','hide', 'd-none']);     //FIXME - what's the BS styles for these
315             
316         }else{
317             Roo.get(this.iframe).removeClass(['x-hidden','hide', 'd-none']);
318             //this.iframe.className = '';
319             this.deferFocus();
320         }
321         //this.setSize(this.owner.wrap.getSize());
322         //this.fireEvent('editmodechange', this, this.sourceEditMode);
323     },
324
325     
326   
327
328     /**
329      * Protected method that will not generally be called directly. If you need/want
330      * custom HTML cleanup, this is the method you should override.
331      * @param {String} html The HTML to be cleaned
332      * return {String} The cleaned HTML
333      */
334     cleanHtml : function(html)
335     {
336         html = String(html);
337         if(html.length > 5){
338             if(Roo.isSafari){ // strip safari nonsense
339                 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
340             }
341         }
342         if(html == '&nbsp;'){
343             html = '';
344         }
345         return html;
346     },
347
348     /**
349      * HTML Editor -> Textarea
350      * Protected method that will not generally be called directly. Syncs the contents
351      * of the editor iframe with the textarea.
352      */
353     syncValue : function()
354     {
355         //Roo.log("HtmlEditorCore:syncValue (EDITOR->TEXT)");
356         if(this.initialized){
357             
358             this.undoManager.addEvent();
359
360             
361             var bd = (this.doc.body || this.doc.documentElement);
362            
363             
364             var sel = this.win.getSelection();
365             
366             var div = document.createElement('div');
367             div.innerHTML = bd.innerHTML;
368             var gtx = div.getElementsByClassName('gtx-trans-icon'); // google translate - really annoying and difficult to get rid of.
369             if (gtx.length > 0) {
370                 var rm = gtx.item(0).parentNode;
371                 rm.parentNode.removeChild(rm);
372             }
373             
374            
375             if (this.enableBlocks) {
376                 new Roo.htmleditor.FilterBlock({ node : div });
377             }
378             //?? tidy?
379             var tidy = new Roo.htmleditor.TidySerializer({
380                 inner:  true
381             });
382             var html  = tidy.serialize(div);
383             
384             
385             if(Roo.isSafari){
386                 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
387                 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
388                 if(m && m[1]){
389                     html = '<div style="'+m[0]+'">' + html + '</div>';
390                 }
391             }
392             html = this.cleanHtml(html);
393             // fix up the special chars.. normaly like back quotes in word...
394             // however we do not want to do this with chinese..
395             html = html.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u0080-\uFFFF]/g, function(match) {
396                 
397                 var cc = match.charCodeAt();
398
399                 // Get the character value, handling surrogate pairs
400                 if (match.length == 2) {
401                     // It's a surrogate pair, calculate the Unicode code point
402                     var high = match.charCodeAt(0) - 0xD800;
403                     var low  = match.charCodeAt(1) - 0xDC00;
404                     cc = (high * 0x400) + low + 0x10000;
405                 }  else if (
406                     (cc >= 0x4E00 && cc < 0xA000 ) ||
407                     (cc >= 0x3400 && cc < 0x4E00 ) ||
408                     (cc >= 0xf900 && cc < 0xfb00 )
409                 ) {
410                         return match;
411                 }  
412          
413                 // No, use a numeric entity. Here we brazenly (and possibly mistakenly)
414                 return "&#" + cc + ";";
415                 
416                 
417             });
418             
419             
420              
421             if(this.owner.fireEvent('beforesync', this, html) !== false){
422                 this.el.dom.value = html;
423                 this.owner.fireEvent('sync', this, html);
424             }
425         }
426     },
427
428     /**
429      * TEXTAREA -> EDITABLE
430      * Protected method that will not generally be called directly. Pushes the value of the textarea
431      * into the iframe editor.
432      */
433     pushValue : function()
434     {
435         //Roo.log("HtmlEditorCore:pushValue (TEXT->EDITOR)");
436         if(this.initialized){
437             var v = this.el.dom.value.trim();
438             
439             
440             if(this.owner.fireEvent('beforepush', this, v) !== false){
441                 var d = (this.doc.body || this.doc.documentElement);
442                 d.innerHTML = v;
443                  
444                 this.el.dom.value = d.innerHTML;
445                 this.owner.fireEvent('push', this, v);
446             }
447             if (this.autoClean) {
448                 new Roo.htmleditor.FilterParagraph({node : this.doc.body}); // paragraphs
449                 new Roo.htmleditor.FilterSpan({node : this.doc.body}); // empty spans
450             }
451             if (this.enableBlocks) {
452                 Roo.htmleditor.Block.initAll(this.doc.body);
453             }
454             
455             this.updateLanguage();
456             
457             var lc = this.doc.body.lastChild;
458             if (lc && lc.nodeType == 1 && lc.getAttribute("contenteditable") == "false") {
459                 // add an extra line at the end.
460                 this.doc.body.appendChild(this.doc.createElement('br'));
461             }
462             
463             
464         }
465     },
466
467     // private
468     deferFocus : function(){
469         this.focus.defer(10, this);
470     },
471
472     // doc'ed in Field
473     focus : function(){
474         if(this.win && !this.sourceEditMode){
475             this.win.focus();
476         }else{
477             this.el.focus();
478         }
479     },
480     
481     assignDocWin: function()
482     {
483         var iframe = this.iframe;
484         
485          if(Roo.isIE){
486             this.doc = iframe.contentWindow.document;
487             this.win = iframe.contentWindow;
488         } else {
489 //            if (!Roo.get(this.frameId)) {
490 //                return;
491 //            }
492 //            this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
493 //            this.win = Roo.get(this.frameId).dom.contentWindow;
494             
495             if (!Roo.get(this.frameId) && !iframe.contentDocument) {
496                 return;
497             }
498             
499             this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
500             this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
501         }
502     },
503     
504     // private
505     initEditor : function(){
506         //console.log("INIT EDITOR");
507         this.assignDocWin();
508         
509         
510         
511         this.doc.designMode="on";
512         this.doc.open();
513         this.doc.write(this.getDocMarkup());
514         this.doc.close();
515         
516         var dbody = (this.doc.body || this.doc.documentElement);
517         //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
518         // this copies styles from the containing element into thsi one..
519         // not sure why we need all of this..
520         //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
521         
522         //var ss = this.el.getStyles( 'background-image', 'background-repeat');
523         //ss['background-attachment'] = 'fixed'; // w3c
524         dbody.bgProperties = 'fixed'; // ie
525         dbody.setAttribute("translate", "no");
526         
527         //Roo.DomHelper.applyStyles(dbody, ss);
528         Roo.EventManager.on(this.doc, {
529              
530             'mouseup': this.onEditorEvent,
531             'dblclick': this.onEditorEvent,
532             'click': this.onEditorEvent,
533             'keyup': this.onEditorEvent,
534             
535             buffer:100,
536             scope: this
537         });
538         Roo.EventManager.on(this.doc, {
539             'paste': this.onPasteEvent,
540             scope : this
541         });
542         if(Roo.isGecko){
543             Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
544         }
545         //??? needed???
546         if(Roo.isIE || Roo.isSafari || Roo.isOpera){
547             Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
548         }
549         this.initialized = true;
550
551         
552         // initialize special key events - enter
553         new Roo.htmleditor.KeyEnter({core : this});
554         
555          
556         
557         this.owner.fireEvent('initialize', this);
558         this.pushValue();
559     },
560     // this is to prevent a href clicks resulting in a redirect?
561    
562     onPasteEvent : function(e,v)
563     {
564         // I think we better assume paste is going to be a dirty load of rubish from word..
565         
566         // even pasting into a 'email version' of this widget will have to clean up that mess.
567         var cd = (e.browserEvent.clipboardData || window.clipboardData);
568         
569         // check what type of paste - if it's an image, then handle it differently.
570         if (cd.files && cd.files.length > 0) {
571             // pasting images?
572             var urlAPI = (window.createObjectURL && window) || 
573                 (window.URL && URL.revokeObjectURL && URL) || 
574                 (window.webkitURL && webkitURL);
575     
576             var url = urlAPI.createObjectURL( cd.files[0]);
577             this.insertAtCursor('<img src=" + url + ">');
578             return false;
579         }
580         if (cd.types.indexOf('text/html') < 0 ) {
581             return false;
582         }
583         var images = [];
584         var html = cd.getData('text/html'); // clipboard event
585         if (cd.types.indexOf('text/rtf') > -1) {
586             var parser = new Roo.rtf.Parser(cd.getData('text/rtf'));
587             images = parser.doc ? parser.doc.getElementsByType('pict') : [];
588         }
589         Roo.log(images);
590         //Roo.log(imgs);
591         // fixme..
592         images = images.filter(function(g) { return !g.path.match(/^rtf\/(head|pgdsctbl|listtable|footerf)/); }) // ignore headers/footers etc.
593                        .map(function(g) { return g.toDataURL(); })
594                        .filter(function(g) { return g != 'about:blank'; });
595         
596         
597         html = this.cleanWordChars(html);
598         
599         var d = (new DOMParser().parseFromString(html, 'text/html')).body;
600         
601         
602         var sn = this.getParentElement();
603         // check if d contains a table, and prevent nesting??
604         //Roo.log(d.getElementsByTagName('table'));
605         //Roo.log(sn);
606         //Roo.log(sn.closest('table'));
607         if (d.getElementsByTagName('table').length && sn && sn.closest('table')) {
608             e.preventDefault();
609             this.insertAtCursor("You can not nest tables");
610             //Roo.log("prevent?"); // fixme - 
611             return false;
612         }
613         
614         if (images.length > 0) {
615             Roo.each(d.getElementsByTagName('img'), function(img, i) {
616                 img.setAttribute('src', images[i]);
617             });
618         }
619         if (this.autoClean) {
620             new Roo.htmleditor.FilterStyleToTag({ node : d });
621             new Roo.htmleditor.FilterAttributes({
622                 node : d,
623                 attrib_white : ['href', 'src', 'name', 'align', 'colspan', 'rowspan', 'data-display'],
624                 attrib_clean : ['href', 'src' ] 
625             });
626             new Roo.htmleditor.FilterBlack({ node : d, tag : this.black});
627             // should be fonts..
628             new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT', 'O:P' ]} );
629             new Roo.htmleditor.FilterParagraph({ node : d });
630             new Roo.htmleditor.FilterSpan({ node : d });
631             new Roo.htmleditor.FilterLongBr({ node : d });
632             new Roo.htmleditor.FilterComment({ node : d });
633         }
634         if (this.enableBlocks) {
635                 
636             Array.from(d.getElementsByTagName('img')).forEach(function(img) {
637                 if (img.closest('figure')) { // assume!! that it's aready
638                     return;
639                 }
640                 var fig  = new Roo.htmleditor.BlockFigure({
641                     image_src  : img.src
642                 });
643                 fig.updateElement(img); // replace it..
644                 
645             });
646         }
647         
648         
649         this.insertAtCursor(d.innerHTML.replace(/&nbsp;/g,' '));
650         if (this.enableBlocks) {
651             Roo.htmleditor.Block.initAll(this.doc.body);
652         }
653         
654         
655         e.preventDefault();
656         return false;
657         // default behaveiour should be our local cleanup paste? (optional?)
658         // for simple editor - we want to hammer the paste and get rid of everything... - so over-rideable..
659         //this.owner.fireEvent('paste', e, v);
660     },
661     // private
662     onDestroy : function(){
663         
664         
665         
666         if(this.rendered){
667             
668             //for (var i =0; i < this.toolbars.length;i++) {
669             //    // fixme - ask toolbars for heights?
670             //    this.toolbars[i].onDestroy();
671            // }
672             
673             //this.wrap.dom.innerHTML = '';
674             //this.wrap.remove();
675         }
676     },
677
678     // private
679     onFirstFocus : function(){
680         
681         this.assignDocWin();
682         this.undoManager = new Roo.lib.UndoManager(100,(this.doc.body || this.doc.documentElement));
683         
684         this.activated = true;
685          
686     
687         if(Roo.isGecko){ // prevent silly gecko errors
688             this.win.focus();
689             var s = this.win.getSelection();
690             if(!s.focusNode || s.focusNode.nodeType != 3){
691                 var r = s.getRangeAt(0);
692                 r.selectNodeContents((this.doc.body || this.doc.documentElement));
693                 r.collapse(true);
694                 this.deferFocus();
695             }
696             try{
697                 this.execCmd('useCSS', true);
698                 this.execCmd('styleWithCSS', false);
699             }catch(e){}
700         }
701         this.owner.fireEvent('activate', this);
702     },
703
704     // private
705     adjustFont: function(btn){
706         var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
707         //if(Roo.isSafari){ // safari
708         //    adjust *= 2;
709        // }
710         var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
711         if(Roo.isSafari){ // safari
712             var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
713             v =  (v < 10) ? 10 : v;
714             v =  (v > 48) ? 48 : v;
715             v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
716             
717         }
718         
719         
720         v = Math.max(1, v+adjust);
721         
722         this.execCmd('FontSize', v  );
723     },
724
725     onEditorEvent : function(e)
726     {
727          
728         
729         if (e && (e.ctrlKey || e.metaKey) && e.keyCode === 90) {
730             return; // we do not handle this.. (undo manager does..)
731         }
732         // in theory this detects if the last element is not a br, then we try and do that.
733         // its so clicking in space at bottom triggers adding a br and moving the cursor.
734         if (e &&
735             e.target.nodeName == 'BODY' &&
736             e.type == "mouseup" &&
737             this.doc.body.lastChild
738            ) {
739             var lc = this.doc.body.lastChild;
740             // gtx-trans is google translate plugin adding crap.
741             while ((lc.nodeType == 3 && lc.nodeValue == '') || lc.id == 'gtx-trans') {
742                 lc = lc.previousSibling;
743             }
744             if (lc.nodeType == 1 && lc.nodeName != 'BR') {
745             // if last element is <BR> - then dont do anything.
746             
747                 var ns = this.doc.createElement('br');
748                 this.doc.body.appendChild(ns);
749                 range = this.doc.createRange();
750                 range.setStartAfter(ns);
751                 range.collapse(true);
752                 var sel = this.win.getSelection();
753                 sel.removeAllRanges();
754                 sel.addRange(range);
755             }
756         }
757         
758         
759         
760         this.fireEditorEvent(e);
761       //  this.updateToolbar();
762         this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
763     },
764     
765     fireEditorEvent: function(e)
766     {
767         this.owner.fireEvent('editorevent', this, e);
768     },
769
770     insertTag : function(tg)
771     {
772         // could be a bit smarter... -> wrap the current selected tRoo..
773         if (tg.toLowerCase() == 'span' ||
774             tg.toLowerCase() == 'code' ||
775             tg.toLowerCase() == 'sup' ||
776             tg.toLowerCase() == 'sub' 
777             ) {
778             
779             range = this.createRange(this.getSelection());
780             var wrappingNode = this.doc.createElement(tg.toLowerCase());
781             wrappingNode.appendChild(range.extractContents());
782             range.insertNode(wrappingNode);
783
784             return;
785             
786             
787             
788         }
789         this.execCmd("formatblock",   tg);
790         this.undoManager.addEvent(); 
791     },
792     
793     insertText : function(txt)
794     {
795         
796         
797         var range = this.createRange();
798         range.deleteContents();
799                //alert(Sender.getAttribute('label'));
800                
801         range.insertNode(this.doc.createTextNode(txt));
802         this.undoManager.addEvent();
803     } ,
804     
805      
806
807     /**
808      * Executes a Midas editor command on the editor document and performs necessary focus and
809      * toolbar updates. <b>This should only be called after the editor is initialized.</b>
810      * @param {String} cmd The Midas command
811      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
812      */
813     relayCmd : function(cmd, value)
814     {
815         
816         switch (cmd) {
817             case 'justifyleft':
818             case 'justifyright':
819             case 'justifycenter':
820                 // if we are in a cell, then we will adjust the
821                 var n = this.getParentElement();
822                 var td = n.closest('td');
823                 if (td) {
824                     var bl = Roo.htmleditor.Block.factory(td);
825                     bl.textAlign = cmd.replace('justify','');
826                     bl.updateElement();
827                     this.owner.fireEvent('editorevent', this);
828                     return;
829                 }
830                 this.execCmd('styleWithCSS', true); // 
831                 break;
832             case 'bold':
833             case 'italic':
834                 // if there is no selection, then we insert, and set the curson inside it..
835                 this.execCmd('styleWithCSS', false); 
836                 break;
837                 
838         
839             default:
840                 break;
841         }
842         
843         
844         this.win.focus();
845         this.execCmd(cmd, value);
846         this.owner.fireEvent('editorevent', this);
847         //this.updateToolbar();
848         this.owner.deferFocus();
849     },
850
851     /**
852      * Executes a Midas editor command directly on the editor document.
853      * For visual commands, you should use {@link #relayCmd} instead.
854      * <b>This should only be called after the editor is initialized.</b>
855      * @param {String} cmd The Midas command
856      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
857      */
858     execCmd : function(cmd, value){
859         this.doc.execCommand(cmd, false, value === undefined ? null : value);
860         this.syncValue();
861     },
862  
863  
864    
865     /**
866      * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
867      * to insert tRoo.
868      * @param {String} text | dom node.. 
869      */
870     insertAtCursor : function(text)
871     {
872         
873         if(!this.activated){
874             return;
875         }
876          
877         if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
878             this.win.focus();
879             
880             
881             // from jquery ui (MIT licenced)
882             var range, node;
883             var win = this.win;
884             
885             if (win.getSelection && win.getSelection().getRangeAt) {
886                 
887                 // delete the existing?
888                 
889                 this.createRange(this.getSelection()).deleteContents();
890                 range = win.getSelection().getRangeAt(0);
891                 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
892                 range.insertNode(node);
893                 range = range.cloneRange();
894                 range.collapse(false);
895                  
896                 win.getSelection().removeAllRanges();
897                 win.getSelection().addRange(range);
898                 
899                 
900                 
901             } else if (win.document.selection && win.document.selection.createRange) {
902                 // no firefox support
903                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
904                 win.document.selection.createRange().pasteHTML(txt);
905             
906             } else {
907                 // no firefox support
908                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
909                 this.execCmd('InsertHTML', txt);
910             } 
911             this.syncValue();
912             
913             this.deferFocus();
914         }
915     },
916  // private
917     mozKeyPress : function(e){
918         if(e.ctrlKey){
919             var c = e.getCharCode(), cmd;
920           
921             if(c > 0){
922                 c = String.fromCharCode(c).toLowerCase();
923                 switch(c){
924                     case 'b':
925                         cmd = 'bold';
926                         break;
927                     case 'i':
928                         cmd = 'italic';
929                         break;
930                     
931                     case 'u':
932                         cmd = 'underline';
933                         break;
934                     
935                     //case 'v':
936                       //  this.cleanUpPaste.defer(100, this);
937                       //  return;
938                         
939                 }
940                 if(cmd){
941                     
942                     this.relayCmd(cmd);
943                     //this.win.focus();
944                     //this.execCmd(cmd);
945                     //this.deferFocus();
946                     e.preventDefault();
947                 }
948                 
949             }
950         }
951     },
952
953     // private
954     fixKeys : function(){ // load time branching for fastest keydown performance
955         
956         
957         if(Roo.isIE){
958             return function(e){
959                 var k = e.getKey(), r;
960                 if(k == e.TAB){
961                     e.stopEvent();
962                     r = this.doc.selection.createRange();
963                     if(r){
964                         r.collapse(true);
965                         r.pasteHTML('&#160;&#160;&#160;&#160;');
966                         this.deferFocus();
967                     }
968                     return;
969                 }
970                 /// this is handled by Roo.htmleditor.KeyEnter
971                  /*
972                 if(k == e.ENTER){
973                     r = this.doc.selection.createRange();
974                     if(r){
975                         var target = r.parentElement();
976                         if(!target || target.tagName.toLowerCase() != 'li'){
977                             e.stopEvent();
978                             r.pasteHTML('<br/>');
979                             r.collapse(false);
980                             r.select();
981                         }
982                     }
983                 }
984                 */
985                 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
986                 //    this.cleanUpPaste.defer(100, this);
987                 //    return;
988                 //}
989                 
990                 
991             };
992         }else if(Roo.isOpera){
993             return function(e){
994                 var k = e.getKey();
995                 if(k == e.TAB){
996                     e.stopEvent();
997                     this.win.focus();
998                     this.execCmd('InsertHTML','&#160;&#160;&#160;&#160;');
999                     this.deferFocus();
1000                 }
1001                
1002                 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
1003                 //    this.cleanUpPaste.defer(100, this);
1004                  //   return;
1005                 //}
1006                 
1007             };
1008         }else if(Roo.isSafari){
1009             return function(e){
1010                 var k = e.getKey();
1011                 
1012                 if(k == e.TAB){
1013                     e.stopEvent();
1014                     this.execCmd('InsertText','\t');
1015                     this.deferFocus();
1016                     return;
1017                 }
1018                  this.mozKeyPress(e);
1019                 
1020                //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
1021                  //   this.cleanUpPaste.defer(100, this);
1022                  //   return;
1023                // }
1024                 
1025              };
1026         }
1027     }(),
1028     
1029     getAllAncestors: function()
1030     {
1031         var p = this.getSelectedNode();
1032         var a = [];
1033         if (!p) {
1034             a.push(p); // push blank onto stack..
1035             p = this.getParentElement();
1036         }
1037         
1038         
1039         while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
1040             a.push(p);
1041             p = p.parentNode;
1042         }
1043         a.push(this.doc.body);
1044         return a;
1045     },
1046     lastSel : false,
1047     lastSelNode : false,
1048     
1049     
1050     getSelection : function() 
1051     {
1052         this.assignDocWin();
1053         return Roo.lib.Selection.wrap(Roo.isIE ? this.doc.selection : this.win.getSelection(), this.doc);
1054     },
1055     /**
1056      * Select a dom node
1057      * @param {DomElement} node the node to select
1058      */
1059     selectNode : function(node, collapse)
1060     {
1061         var nodeRange = node.ownerDocument.createRange();
1062         try {
1063             nodeRange.selectNode(node);
1064         } catch (e) {
1065             nodeRange.selectNodeContents(node);
1066         }
1067         if (collapse === true) {
1068             nodeRange.collapse(true);
1069         }
1070         //
1071         var s = this.win.getSelection();
1072         s.removeAllRanges();
1073         s.addRange(nodeRange);
1074     },
1075     
1076     getSelectedNode: function() 
1077     {
1078         // this may only work on Gecko!!!
1079         
1080         // should we cache this!!!!
1081         
1082          
1083          
1084         var range = this.createRange(this.getSelection()).cloneRange();
1085         
1086         if (Roo.isIE) {
1087             var parent = range.parentElement();
1088             while (true) {
1089                 var testRange = range.duplicate();
1090                 testRange.moveToElementText(parent);
1091                 if (testRange.inRange(range)) {
1092                     break;
1093                 }
1094                 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
1095                     break;
1096                 }
1097                 parent = parent.parentElement;
1098             }
1099             return parent;
1100         }
1101         
1102         // is ancestor a text element.
1103         var ac =  range.commonAncestorContainer;
1104         if (ac.nodeType == 3) {
1105             ac = ac.parentNode;
1106         }
1107         
1108         var ar = ac.childNodes;
1109          
1110         var nodes = [];
1111         var other_nodes = [];
1112         var has_other_nodes = false;
1113         for (var i=0;i<ar.length;i++) {
1114             if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ? 
1115                 continue;
1116             }
1117             // fullly contained node.
1118             
1119             if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
1120                 nodes.push(ar[i]);
1121                 continue;
1122             }
1123             
1124             // probably selected..
1125             if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
1126                 other_nodes.push(ar[i]);
1127                 continue;
1128             }
1129             // outer..
1130             if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0))  {
1131                 continue;
1132             }
1133             
1134             
1135             has_other_nodes = true;
1136         }
1137         if (!nodes.length && other_nodes.length) {
1138             nodes= other_nodes;
1139         }
1140         if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
1141             return false;
1142         }
1143         
1144         return nodes[0];
1145     },
1146     
1147     
1148     createRange: function(sel)
1149     {
1150         // this has strange effects when using with 
1151         // top toolbar - not sure if it's a great idea.
1152         //this.editor.contentWindow.focus();
1153         if (typeof sel != "undefined") {
1154             try {
1155                 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
1156             } catch(e) {
1157                 return this.doc.createRange();
1158             }
1159         } else {
1160             return this.doc.createRange();
1161         }
1162     },
1163     getParentElement: function()
1164     {
1165         
1166         this.assignDocWin();
1167         var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
1168         
1169         var range = this.createRange(sel);
1170          
1171         try {
1172             var p = range.commonAncestorContainer;
1173             while (p.nodeType == 3) { // text node
1174                 p = p.parentNode;
1175             }
1176             return p;
1177         } catch (e) {
1178             return null;
1179         }
1180     
1181     },
1182     /***
1183      *
1184      * Range intersection.. the hard stuff...
1185      *  '-1' = before
1186      *  '0' = hits..
1187      *  '1' = after.
1188      *         [ -- selected range --- ]
1189      *   [fail]                        [fail]
1190      *
1191      *    basically..
1192      *      if end is before start or  hits it. fail.
1193      *      if start is after end or hits it fail.
1194      *
1195      *   if either hits (but other is outside. - then it's not 
1196      *   
1197      *    
1198      **/
1199     
1200     
1201     // @see http://www.thismuchiknow.co.uk/?p=64.
1202     rangeIntersectsNode : function(range, node)
1203     {
1204         var nodeRange = node.ownerDocument.createRange();
1205         try {
1206             nodeRange.selectNode(node);
1207         } catch (e) {
1208             nodeRange.selectNodeContents(node);
1209         }
1210     
1211         var rangeStartRange = range.cloneRange();
1212         rangeStartRange.collapse(true);
1213     
1214         var rangeEndRange = range.cloneRange();
1215         rangeEndRange.collapse(false);
1216     
1217         var nodeStartRange = nodeRange.cloneRange();
1218         nodeStartRange.collapse(true);
1219     
1220         var nodeEndRange = nodeRange.cloneRange();
1221         nodeEndRange.collapse(false);
1222     
1223         return rangeStartRange.compareBoundaryPoints(
1224                  Range.START_TO_START, nodeEndRange) == -1 &&
1225                rangeEndRange.compareBoundaryPoints(
1226                  Range.START_TO_START, nodeStartRange) == 1;
1227         
1228          
1229     },
1230     rangeCompareNode : function(range, node)
1231     {
1232         var nodeRange = node.ownerDocument.createRange();
1233         try {
1234             nodeRange.selectNode(node);
1235         } catch (e) {
1236             nodeRange.selectNodeContents(node);
1237         }
1238         
1239         
1240         range.collapse(true);
1241     
1242         nodeRange.collapse(true);
1243      
1244         var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1245         var ee = range.compareBoundaryPoints(  Range.END_TO_END, nodeRange);
1246          
1247         //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1248         
1249         var nodeIsBefore   =  ss == 1;
1250         var nodeIsAfter    = ee == -1;
1251         
1252         if (nodeIsBefore && nodeIsAfter) {
1253             return 0; // outer
1254         }
1255         if (!nodeIsBefore && nodeIsAfter) {
1256             return 1; //right trailed.
1257         }
1258         
1259         if (nodeIsBefore && !nodeIsAfter) {
1260             return 2;  // left trailed.
1261         }
1262         // fully contined.
1263         return 3;
1264     },
1265  
1266     cleanWordChars : function(input) {// change the chars to hex code
1267         
1268        var swapCodes  = [ 
1269             [    8211, "&#8211;" ], 
1270             [    8212, "&#8212;" ], 
1271             [    8216,  "'" ],  
1272             [    8217, "'" ],  
1273             [    8220, '"' ],  
1274             [    8221, '"' ],  
1275             [    8226, "*" ],  
1276             [    8230, "..." ]
1277         ]; 
1278         var output = input;
1279         Roo.each(swapCodes, function(sw) { 
1280             var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1281             
1282             output = output.replace(swapper, sw[1]);
1283         });
1284         
1285         return output;
1286     },
1287     
1288      
1289     
1290         
1291     
1292     cleanUpChild : function (node)
1293     {
1294         
1295         new Roo.htmleditor.FilterComment({node : node});
1296         new Roo.htmleditor.FilterAttributes({
1297                 node : node,
1298                 attrib_black : this.ablack,
1299                 attrib_clean : this.aclean,
1300                 style_white : this.cwhite,
1301                 style_black : this.cblack
1302         });
1303         new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
1304         new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
1305          
1306         
1307     },
1308     
1309     /**
1310      * Clean up MS wordisms...
1311      * @deprecated - use filter directly
1312      */
1313     cleanWord : function(node)
1314     {
1315         new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
1316         
1317     },
1318    
1319     
1320     /**
1321
1322      * @deprecated - use filters
1323      */
1324     cleanTableWidths : function(node)
1325     {
1326         new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
1327         
1328  
1329     },
1330     
1331      
1332         
1333     applyBlacklists : function()
1334     {
1335         var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white  : [];
1336         var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black :  [];
1337         
1338         this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean :  Roo.HtmlEditorCore.aclean;
1339         this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack :  Roo.HtmlEditorCore.ablack;
1340         this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove :  Roo.HtmlEditorCore.tag_remove;
1341         
1342         this.white = [];
1343         this.black = [];
1344         Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1345             if (b.indexOf(tag) > -1) {
1346                 return;
1347             }
1348             this.white.push(tag);
1349             
1350         }, this);
1351         
1352         Roo.each(w, function(tag) {
1353             if (b.indexOf(tag) > -1) {
1354                 return;
1355             }
1356             if (this.white.indexOf(tag) > -1) {
1357                 return;
1358             }
1359             this.white.push(tag);
1360             
1361         }, this);
1362         
1363         
1364         Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1365             if (w.indexOf(tag) > -1) {
1366                 return;
1367             }
1368             this.black.push(tag);
1369             
1370         }, this);
1371         
1372         Roo.each(b, function(tag) {
1373             if (w.indexOf(tag) > -1) {
1374                 return;
1375             }
1376             if (this.black.indexOf(tag) > -1) {
1377                 return;
1378             }
1379             this.black.push(tag);
1380             
1381         }, this);
1382         
1383         
1384         w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite  : [];
1385         b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack :  [];
1386         
1387         this.cwhite = [];
1388         this.cblack = [];
1389         Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1390             if (b.indexOf(tag) > -1) {
1391                 return;
1392             }
1393             this.cwhite.push(tag);
1394             
1395         }, this);
1396         
1397         Roo.each(w, function(tag) {
1398             if (b.indexOf(tag) > -1) {
1399                 return;
1400             }
1401             if (this.cwhite.indexOf(tag) > -1) {
1402                 return;
1403             }
1404             this.cwhite.push(tag);
1405             
1406         }, this);
1407         
1408         
1409         Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1410             if (w.indexOf(tag) > -1) {
1411                 return;
1412             }
1413             this.cblack.push(tag);
1414             
1415         }, this);
1416         
1417         Roo.each(b, function(tag) {
1418             if (w.indexOf(tag) > -1) {
1419                 return;
1420             }
1421             if (this.cblack.indexOf(tag) > -1) {
1422                 return;
1423             }
1424             this.cblack.push(tag);
1425             
1426         }, this);
1427     },
1428     
1429     setStylesheets : function(stylesheets)
1430     {
1431         if(typeof(stylesheets) == 'string'){
1432             Roo.get(this.iframe.contentDocument.head).createChild({
1433                 tag : 'link',
1434                 rel : 'stylesheet',
1435                 type : 'text/css',
1436                 href : stylesheets
1437             });
1438             
1439             return;
1440         }
1441         var _this = this;
1442      
1443         Roo.each(stylesheets, function(s) {
1444             if(!s.length){
1445                 return;
1446             }
1447             
1448             Roo.get(_this.iframe.contentDocument.head).createChild({
1449                 tag : 'link',
1450                 rel : 'stylesheet',
1451                 type : 'text/css',
1452                 href : s
1453             });
1454         });
1455
1456         
1457     },
1458     
1459     
1460     updateLanguage : function()
1461     {
1462         if (!this.iframe || !this.iframe.contentDocument) {
1463             return;
1464         }
1465         Roo.get(this.iframe.contentDocument.body).attr("lang", this.language);
1466     },
1467     
1468     
1469     removeStylesheets : function()
1470     {
1471         var _this = this;
1472         
1473         Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1474             s.remove();
1475         });
1476     },
1477     
1478     setStyle : function(style)
1479     {
1480         Roo.get(this.iframe.contentDocument.head).createChild({
1481             tag : 'style',
1482             type : 'text/css',
1483             html : style
1484         });
1485
1486         return;
1487     }
1488     
1489     // hide stuff that is not compatible
1490     /**
1491      * @event blur
1492      * @hide
1493      */
1494     /**
1495      * @event change
1496      * @hide
1497      */
1498     /**
1499      * @event focus
1500      * @hide
1501      */
1502     /**
1503      * @event specialkey
1504      * @hide
1505      */
1506     /**
1507      * @cfg {String} fieldClass @hide
1508      */
1509     /**
1510      * @cfg {String} focusClass @hide
1511      */
1512     /**
1513      * @cfg {String} autoCreate @hide
1514      */
1515     /**
1516      * @cfg {String} inputType @hide
1517      */
1518     /**
1519      * @cfg {String} invalidClass @hide
1520      */
1521     /**
1522      * @cfg {String} invalidText @hide
1523      */
1524     /**
1525      * @cfg {String} msgFx @hide
1526      */
1527     /**
1528      * @cfg {String} validateOnBlur @hide
1529      */
1530 });
1531
1532 Roo.HtmlEditorCore.white = [
1533         'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
1534         
1535        'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD',      'DIR',       'DIV', 
1536        'DL',      'DT',         'H1',     'H2',      'H3',        'H4', 
1537        'H5',      'H6',         'HR',     'ISINDEX', 'LISTING',   'MARQUEE', 
1538        'MENU',    'MULTICOL',   'OL',     'P',       'PLAINTEXT', 'PRE', 
1539        'TABLE',   'UL',         'XMP', 
1540        
1541        'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH', 
1542       'THEAD',   'TR', 
1543      
1544       'DIR', 'MENU', 'OL', 'UL', 'DL',
1545        
1546       'EMBED',  'OBJECT'
1547 ];
1548
1549
1550 Roo.HtmlEditorCore.black = [
1551     //    'embed',  'object', // enable - backend responsiblity to clean thiese
1552         'APPLET', // 
1553         'BASE',   'BASEFONT', 'BGSOUND', 'BLINK',  'BODY', 
1554         'FRAME',  'FRAMESET', 'HEAD',    'HTML',   'ILAYER', 
1555         'IFRAME', 'LAYER',  'LINK',     'META',    'OBJECT',   
1556         'SCRIPT', 'STYLE' ,'TITLE',  'XML',
1557         //'FONT' // CLEAN LATER..
1558         'COLGROUP', 'COL'   // messy tables.
1559         
1560         
1561 ];
1562 Roo.HtmlEditorCore.clean = [ // ?? needed???
1563      'SCRIPT', 'STYLE', 'TITLE', 'XML'
1564 ];
1565 Roo.HtmlEditorCore.tag_remove = [
1566     'FONT', 'TBODY'  
1567 ];
1568 // attributes..
1569
1570 Roo.HtmlEditorCore.ablack = [
1571     'on'
1572 ];
1573     
1574 Roo.HtmlEditorCore.aclean = [ 
1575     'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc' 
1576 ];
1577
1578 // protocols..
1579 Roo.HtmlEditorCore.pwhite= [
1580         'http',  'https',  'mailto'
1581 ];
1582
1583 // white listed style attributes.
1584 Roo.HtmlEditorCore.cwhite= [
1585       //  'text-align', /// default is to allow most things..
1586       
1587          
1588 //        'font-size'//??
1589 ];
1590
1591 // black listed style attributes.
1592 Roo.HtmlEditorCore.cblack= [
1593       //  'font-size' -- this can be set by the project 
1594 ];
1595
1596
1597
1598
1599