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