filter word documents - add support for converting bullet points
[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.FilterWord({ node : d });
623             
624             new Roo.htmleditor.FilterStyleToTag({ node : d });
625             new Roo.htmleditor.FilterAttributes({
626                 node : d,
627                 attrib_white : ['href', 'src', 'name', 'align', 'colspan', 'rowspan', 'data-display', 'data-width'],
628                 attrib_clean : ['href', 'src' ] 
629             });
630             new Roo.htmleditor.FilterBlack({ node : d, tag : this.black});
631             // should be fonts..
632             new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT', 'O:P' ]} );
633             new Roo.htmleditor.FilterParagraph({ node : d });
634             new Roo.htmleditor.FilterSpan({ node : d });
635             new Roo.htmleditor.FilterLongBr({ node : d });
636             new Roo.htmleditor.FilterComment({ node : d });
637             
638             
639         }
640         if (this.enableBlocks) {
641                 
642             Array.from(d.getElementsByTagName('img')).forEach(function(img) {
643                 if (img.closest('figure')) { // assume!! that it's aready
644                     return;
645                 }
646                 var fig  = new Roo.htmleditor.BlockFigure({
647                     image_src  : img.src
648                 });
649                 fig.updateElement(img); // replace it..
650                 
651             });
652         }
653         
654         
655         this.insertAtCursor(d.innerHTML.replace(/&nbsp;/g,' '));
656         if (this.enableBlocks) {
657             Roo.htmleditor.Block.initAll(this.doc.body);
658         }
659          
660         
661         e.preventDefault();
662         return false;
663         // default behaveiour should be our local cleanup paste? (optional?)
664         // for simple editor - we want to hammer the paste and get rid of everything... - so over-rideable..
665         //this.owner.fireEvent('paste', e, v);
666     },
667     // private
668     onDestroy : function(){
669         
670         
671         
672         if(this.rendered){
673             
674             //for (var i =0; i < this.toolbars.length;i++) {
675             //    // fixme - ask toolbars for heights?
676             //    this.toolbars[i].onDestroy();
677            // }
678             
679             //this.wrap.dom.innerHTML = '';
680             //this.wrap.remove();
681         }
682     },
683
684     // private
685     onFirstFocus : function(){
686         
687         this.assignDocWin();
688         this.undoManager = new Roo.lib.UndoManager(100,(this.doc.body || this.doc.documentElement));
689         
690         this.activated = true;
691          
692     
693         if(Roo.isGecko){ // prevent silly gecko errors
694             this.win.focus();
695             var s = this.win.getSelection();
696             if(!s.focusNode || s.focusNode.nodeType != 3){
697                 var r = s.getRangeAt(0);
698                 r.selectNodeContents((this.doc.body || this.doc.documentElement));
699                 r.collapse(true);
700                 this.deferFocus();
701             }
702             try{
703                 this.execCmd('useCSS', true);
704                 this.execCmd('styleWithCSS', false);
705             }catch(e){}
706         }
707         this.owner.fireEvent('activate', this);
708     },
709
710     // private
711     adjustFont: function(btn){
712         var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
713         //if(Roo.isSafari){ // safari
714         //    adjust *= 2;
715        // }
716         var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
717         if(Roo.isSafari){ // safari
718             var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
719             v =  (v < 10) ? 10 : v;
720             v =  (v > 48) ? 48 : v;
721             v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
722             
723         }
724         
725         
726         v = Math.max(1, v+adjust);
727         
728         this.execCmd('FontSize', v  );
729     },
730
731     onEditorEvent : function(e)
732     {
733          
734         
735         if (e && (e.ctrlKey || e.metaKey) && e.keyCode === 90) {
736             return; // we do not handle this.. (undo manager does..)
737         }
738         // in theory this detects if the last element is not a br, then we try and do that.
739         // its so clicking in space at bottom triggers adding a br and moving the cursor.
740         if (e &&
741             e.target.nodeName == 'BODY' &&
742             e.type == "mouseup" &&
743             this.doc.body.lastChild
744            ) {
745             var lc = this.doc.body.lastChild;
746             // gtx-trans is google translate plugin adding crap.
747             while ((lc.nodeType == 3 && lc.nodeValue == '') || lc.id == 'gtx-trans') {
748                 lc = lc.previousSibling;
749             }
750             if (lc.nodeType == 1 && lc.nodeName != 'BR') {
751             // if last element is <BR> - then dont do anything.
752             
753                 var ns = this.doc.createElement('br');
754                 this.doc.body.appendChild(ns);
755                 range = this.doc.createRange();
756                 range.setStartAfter(ns);
757                 range.collapse(true);
758                 var sel = this.win.getSelection();
759                 sel.removeAllRanges();
760                 sel.addRange(range);
761             }
762         }
763         
764         
765         
766         this.fireEditorEvent(e);
767       //  this.updateToolbar();
768         this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
769     },
770     
771     fireEditorEvent: function(e)
772     {
773         this.owner.fireEvent('editorevent', this, e);
774     },
775
776     insertTag : function(tg)
777     {
778         // could be a bit smarter... -> wrap the current selected tRoo..
779         if (tg.toLowerCase() == 'span' ||
780             tg.toLowerCase() == 'code' ||
781             tg.toLowerCase() == 'sup' ||
782             tg.toLowerCase() == 'sub' 
783             ) {
784             
785             range = this.createRange(this.getSelection());
786             var wrappingNode = this.doc.createElement(tg.toLowerCase());
787             wrappingNode.appendChild(range.extractContents());
788             range.insertNode(wrappingNode);
789
790             return;
791             
792             
793             
794         }
795         this.execCmd("formatblock",   tg);
796         this.undoManager.addEvent(); 
797     },
798     
799     insertText : function(txt)
800     {
801         
802         
803         var range = this.createRange();
804         range.deleteContents();
805                //alert(Sender.getAttribute('label'));
806                
807         range.insertNode(this.doc.createTextNode(txt));
808         this.undoManager.addEvent();
809     } ,
810     
811      
812
813     /**
814      * Executes a Midas editor command on the editor document and performs necessary focus and
815      * toolbar updates. <b>This should only be called after the editor is initialized.</b>
816      * @param {String} cmd The Midas command
817      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
818      */
819     relayCmd : function(cmd, value)
820     {
821         
822         switch (cmd) {
823             case 'justifyleft':
824             case 'justifyright':
825             case 'justifycenter':
826                 // if we are in a cell, then we will adjust the
827                 var n = this.getParentElement();
828                 var td = n.closest('td');
829                 if (td) {
830                     var bl = Roo.htmleditor.Block.factory(td);
831                     bl.textAlign = cmd.replace('justify','');
832                     bl.updateElement();
833                     this.owner.fireEvent('editorevent', this);
834                     return;
835                 }
836                 this.execCmd('styleWithCSS', true); // 
837                 break;
838             case 'bold':
839             case 'italic':
840                 // if there is no selection, then we insert, and set the curson inside it..
841                 this.execCmd('styleWithCSS', false); 
842                 break;
843                 
844         
845             default:
846                 break;
847         }
848         
849         
850         this.win.focus();
851         this.execCmd(cmd, value);
852         this.owner.fireEvent('editorevent', this);
853         //this.updateToolbar();
854         this.owner.deferFocus();
855     },
856
857     /**
858      * Executes a Midas editor command directly on the editor document.
859      * For visual commands, you should use {@link #relayCmd} instead.
860      * <b>This should only be called after the editor is initialized.</b>
861      * @param {String} cmd The Midas command
862      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
863      */
864     execCmd : function(cmd, value){
865         this.doc.execCommand(cmd, false, value === undefined ? null : value);
866         this.syncValue();
867     },
868  
869  
870    
871     /**
872      * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
873      * to insert tRoo.
874      * @param {String} text | dom node.. 
875      */
876     insertAtCursor : function(text)
877     {
878         
879         if(!this.activated){
880             return;
881         }
882          
883         if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
884             this.win.focus();
885             
886             
887             // from jquery ui (MIT licenced)
888             var range, node;
889             var win = this.win;
890             
891             if (win.getSelection && win.getSelection().getRangeAt) {
892                 
893                 // delete the existing?
894                 
895                 this.createRange(this.getSelection()).deleteContents();
896                 range = win.getSelection().getRangeAt(0);
897                 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
898                 range.insertNode(node);
899                 range = range.cloneRange();
900                 range.collapse(false);
901                  
902                 win.getSelection().removeAllRanges();
903                 win.getSelection().addRange(range);
904                 
905                 
906                 
907             } else if (win.document.selection && win.document.selection.createRange) {
908                 // no firefox support
909                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
910                 win.document.selection.createRange().pasteHTML(txt);
911             
912             } else {
913                 // no firefox support
914                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
915                 this.execCmd('InsertHTML', txt);
916             } 
917             this.syncValue();
918             
919             this.deferFocus();
920         }
921     },
922  // private
923     mozKeyPress : function(e){
924         if(e.ctrlKey){
925             var c = e.getCharCode(), cmd;
926           
927             if(c > 0){
928                 c = String.fromCharCode(c).toLowerCase();
929                 switch(c){
930                     case 'b':
931                         cmd = 'bold';
932                         break;
933                     case 'i':
934                         cmd = 'italic';
935                         break;
936                     
937                     case 'u':
938                         cmd = 'underline';
939                         break;
940                     
941                     //case 'v':
942                       //  this.cleanUpPaste.defer(100, this);
943                       //  return;
944                         
945                 }
946                 if(cmd){
947                     
948                     this.relayCmd(cmd);
949                     //this.win.focus();
950                     //this.execCmd(cmd);
951                     //this.deferFocus();
952                     e.preventDefault();
953                 }
954                 
955             }
956         }
957     },
958
959     // private
960     fixKeys : function(){ // load time branching for fastest keydown performance
961         
962         
963         if(Roo.isIE){
964             return function(e){
965                 var k = e.getKey(), r;
966                 if(k == e.TAB){
967                     e.stopEvent();
968                     r = this.doc.selection.createRange();
969                     if(r){
970                         r.collapse(true);
971                         r.pasteHTML('&#160;&#160;&#160;&#160;');
972                         this.deferFocus();
973                     }
974                     return;
975                 }
976                 /// this is handled by Roo.htmleditor.KeyEnter
977                  /*
978                 if(k == e.ENTER){
979                     r = this.doc.selection.createRange();
980                     if(r){
981                         var target = r.parentElement();
982                         if(!target || target.tagName.toLowerCase() != 'li'){
983                             e.stopEvent();
984                             r.pasteHTML('<br/>');
985                             r.collapse(false);
986                             r.select();
987                         }
988                     }
989                 }
990                 */
991                 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
992                 //    this.cleanUpPaste.defer(100, this);
993                 //    return;
994                 //}
995                 
996                 
997             };
998         }else if(Roo.isOpera){
999             return function(e){
1000                 var k = e.getKey();
1001                 if(k == e.TAB){
1002                     e.stopEvent();
1003                     this.win.focus();
1004                     this.execCmd('InsertHTML','&#160;&#160;&#160;&#160;');
1005                     this.deferFocus();
1006                 }
1007                
1008                 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
1009                 //    this.cleanUpPaste.defer(100, this);
1010                  //   return;
1011                 //}
1012                 
1013             };
1014         }else if(Roo.isSafari){
1015             return function(e){
1016                 var k = e.getKey();
1017                 
1018                 if(k == e.TAB){
1019                     e.stopEvent();
1020                     this.execCmd('InsertText','\t');
1021                     this.deferFocus();
1022                     return;
1023                 }
1024                  this.mozKeyPress(e);
1025                 
1026                //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
1027                  //   this.cleanUpPaste.defer(100, this);
1028                  //   return;
1029                // }
1030                 
1031              };
1032         }
1033     }(),
1034     
1035     getAllAncestors: function()
1036     {
1037         var p = this.getSelectedNode();
1038         var a = [];
1039         if (!p) {
1040             a.push(p); // push blank onto stack..
1041             p = this.getParentElement();
1042         }
1043         
1044         
1045         while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
1046             a.push(p);
1047             p = p.parentNode;
1048         }
1049         a.push(this.doc.body);
1050         return a;
1051     },
1052     lastSel : false,
1053     lastSelNode : false,
1054     
1055     
1056     getSelection : function() 
1057     {
1058         this.assignDocWin();
1059         return Roo.lib.Selection.wrap(Roo.isIE ? this.doc.selection : this.win.getSelection(), this.doc);
1060     },
1061     /**
1062      * Select a dom node
1063      * @param {DomElement} node the node to select
1064      */
1065     selectNode : function(node, collapse)
1066     {
1067         var nodeRange = node.ownerDocument.createRange();
1068         try {
1069             nodeRange.selectNode(node);
1070         } catch (e) {
1071             nodeRange.selectNodeContents(node);
1072         }
1073         if (collapse === true) {
1074             nodeRange.collapse(true);
1075         }
1076         //
1077         var s = this.win.getSelection();
1078         s.removeAllRanges();
1079         s.addRange(nodeRange);
1080     },
1081     
1082     getSelectedNode: function() 
1083     {
1084         // this may only work on Gecko!!!
1085         
1086         // should we cache this!!!!
1087         
1088          
1089          
1090         var range = this.createRange(this.getSelection()).cloneRange();
1091         
1092         if (Roo.isIE) {
1093             var parent = range.parentElement();
1094             while (true) {
1095                 var testRange = range.duplicate();
1096                 testRange.moveToElementText(parent);
1097                 if (testRange.inRange(range)) {
1098                     break;
1099                 }
1100                 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
1101                     break;
1102                 }
1103                 parent = parent.parentElement;
1104             }
1105             return parent;
1106         }
1107         
1108         // is ancestor a text element.
1109         var ac =  range.commonAncestorContainer;
1110         if (ac.nodeType == 3) {
1111             ac = ac.parentNode;
1112         }
1113         
1114         var ar = ac.childNodes;
1115          
1116         var nodes = [];
1117         var other_nodes = [];
1118         var has_other_nodes = false;
1119         for (var i=0;i<ar.length;i++) {
1120             if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ? 
1121                 continue;
1122             }
1123             // fullly contained node.
1124             
1125             if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
1126                 nodes.push(ar[i]);
1127                 continue;
1128             }
1129             
1130             // probably selected..
1131             if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
1132                 other_nodes.push(ar[i]);
1133                 continue;
1134             }
1135             // outer..
1136             if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0))  {
1137                 continue;
1138             }
1139             
1140             
1141             has_other_nodes = true;
1142         }
1143         if (!nodes.length && other_nodes.length) {
1144             nodes= other_nodes;
1145         }
1146         if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
1147             return false;
1148         }
1149         
1150         return nodes[0];
1151     },
1152     
1153     
1154     createRange: function(sel)
1155     {
1156         // this has strange effects when using with 
1157         // top toolbar - not sure if it's a great idea.
1158         //this.editor.contentWindow.focus();
1159         if (typeof sel != "undefined") {
1160             try {
1161                 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
1162             } catch(e) {
1163                 return this.doc.createRange();
1164             }
1165         } else {
1166             return this.doc.createRange();
1167         }
1168     },
1169     getParentElement: function()
1170     {
1171         
1172         this.assignDocWin();
1173         var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
1174         
1175         var range = this.createRange(sel);
1176          
1177         try {
1178             var p = range.commonAncestorContainer;
1179             while (p.nodeType == 3) { // text node
1180                 p = p.parentNode;
1181             }
1182             return p;
1183         } catch (e) {
1184             return null;
1185         }
1186     
1187     },
1188     /***
1189      *
1190      * Range intersection.. the hard stuff...
1191      *  '-1' = before
1192      *  '0' = hits..
1193      *  '1' = after.
1194      *         [ -- selected range --- ]
1195      *   [fail]                        [fail]
1196      *
1197      *    basically..
1198      *      if end is before start or  hits it. fail.
1199      *      if start is after end or hits it fail.
1200      *
1201      *   if either hits (but other is outside. - then it's not 
1202      *   
1203      *    
1204      **/
1205     
1206     
1207     // @see http://www.thismuchiknow.co.uk/?p=64.
1208     rangeIntersectsNode : function(range, node)
1209     {
1210         var nodeRange = node.ownerDocument.createRange();
1211         try {
1212             nodeRange.selectNode(node);
1213         } catch (e) {
1214             nodeRange.selectNodeContents(node);
1215         }
1216     
1217         var rangeStartRange = range.cloneRange();
1218         rangeStartRange.collapse(true);
1219     
1220         var rangeEndRange = range.cloneRange();
1221         rangeEndRange.collapse(false);
1222     
1223         var nodeStartRange = nodeRange.cloneRange();
1224         nodeStartRange.collapse(true);
1225     
1226         var nodeEndRange = nodeRange.cloneRange();
1227         nodeEndRange.collapse(false);
1228     
1229         return rangeStartRange.compareBoundaryPoints(
1230                  Range.START_TO_START, nodeEndRange) == -1 &&
1231                rangeEndRange.compareBoundaryPoints(
1232                  Range.START_TO_START, nodeStartRange) == 1;
1233         
1234          
1235     },
1236     rangeCompareNode : function(range, node)
1237     {
1238         var nodeRange = node.ownerDocument.createRange();
1239         try {
1240             nodeRange.selectNode(node);
1241         } catch (e) {
1242             nodeRange.selectNodeContents(node);
1243         }
1244         
1245         
1246         range.collapse(true);
1247     
1248         nodeRange.collapse(true);
1249      
1250         var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1251         var ee = range.compareBoundaryPoints(  Range.END_TO_END, nodeRange);
1252          
1253         //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1254         
1255         var nodeIsBefore   =  ss == 1;
1256         var nodeIsAfter    = ee == -1;
1257         
1258         if (nodeIsBefore && nodeIsAfter) {
1259             return 0; // outer
1260         }
1261         if (!nodeIsBefore && nodeIsAfter) {
1262             return 1; //right trailed.
1263         }
1264         
1265         if (nodeIsBefore && !nodeIsAfter) {
1266             return 2;  // left trailed.
1267         }
1268         // fully contined.
1269         return 3;
1270     },
1271  
1272     cleanWordChars : function(input) {// change the chars to hex code
1273         
1274        var swapCodes  = [ 
1275             [    8211, "&#8211;" ], 
1276             [    8212, "&#8212;" ], 
1277             [    8216,  "'" ],  
1278             [    8217, "'" ],  
1279             [    8220, '"' ],  
1280             [    8221, '"' ],  
1281             [    8226, "*" ],  
1282             [    8230, "..." ]
1283         ]; 
1284         var output = input;
1285         Roo.each(swapCodes, function(sw) { 
1286             var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1287             
1288             output = output.replace(swapper, sw[1]);
1289         });
1290         
1291         return output;
1292     },
1293     
1294      
1295     
1296         
1297     
1298     cleanUpChild : function (node)
1299     {
1300         
1301         new Roo.htmleditor.FilterComment({node : node});
1302         new Roo.htmleditor.FilterAttributes({
1303                 node : node,
1304                 attrib_black : this.ablack,
1305                 attrib_clean : this.aclean,
1306                 style_white : this.cwhite,
1307                 style_black : this.cblack
1308         });
1309         new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
1310         new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
1311          
1312         
1313     },
1314     
1315     /**
1316      * Clean up MS wordisms...
1317      * @deprecated - use filter directly
1318      */
1319     cleanWord : function(node)
1320     {
1321         new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
1322         
1323     },
1324    
1325     
1326     /**
1327
1328      * @deprecated - use filters
1329      */
1330     cleanTableWidths : function(node)
1331     {
1332         new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
1333         
1334  
1335     },
1336     
1337      
1338         
1339     applyBlacklists : function()
1340     {
1341         var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white  : [];
1342         var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black :  [];
1343         
1344         this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean :  Roo.HtmlEditorCore.aclean;
1345         this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack :  Roo.HtmlEditorCore.ablack;
1346         this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove :  Roo.HtmlEditorCore.tag_remove;
1347         
1348         this.white = [];
1349         this.black = [];
1350         Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1351             if (b.indexOf(tag) > -1) {
1352                 return;
1353             }
1354             this.white.push(tag);
1355             
1356         }, this);
1357         
1358         Roo.each(w, function(tag) {
1359             if (b.indexOf(tag) > -1) {
1360                 return;
1361             }
1362             if (this.white.indexOf(tag) > -1) {
1363                 return;
1364             }
1365             this.white.push(tag);
1366             
1367         }, this);
1368         
1369         
1370         Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1371             if (w.indexOf(tag) > -1) {
1372                 return;
1373             }
1374             this.black.push(tag);
1375             
1376         }, this);
1377         
1378         Roo.each(b, function(tag) {
1379             if (w.indexOf(tag) > -1) {
1380                 return;
1381             }
1382             if (this.black.indexOf(tag) > -1) {
1383                 return;
1384             }
1385             this.black.push(tag);
1386             
1387         }, this);
1388         
1389         
1390         w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite  : [];
1391         b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack :  [];
1392         
1393         this.cwhite = [];
1394         this.cblack = [];
1395         Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1396             if (b.indexOf(tag) > -1) {
1397                 return;
1398             }
1399             this.cwhite.push(tag);
1400             
1401         }, this);
1402         
1403         Roo.each(w, function(tag) {
1404             if (b.indexOf(tag) > -1) {
1405                 return;
1406             }
1407             if (this.cwhite.indexOf(tag) > -1) {
1408                 return;
1409             }
1410             this.cwhite.push(tag);
1411             
1412         }, this);
1413         
1414         
1415         Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1416             if (w.indexOf(tag) > -1) {
1417                 return;
1418             }
1419             this.cblack.push(tag);
1420             
1421         }, this);
1422         
1423         Roo.each(b, function(tag) {
1424             if (w.indexOf(tag) > -1) {
1425                 return;
1426             }
1427             if (this.cblack.indexOf(tag) > -1) {
1428                 return;
1429             }
1430             this.cblack.push(tag);
1431             
1432         }, this);
1433     },
1434     
1435     setStylesheets : function(stylesheets)
1436     {
1437         if(typeof(stylesheets) == 'string'){
1438             Roo.get(this.iframe.contentDocument.head).createChild({
1439                 tag : 'link',
1440                 rel : 'stylesheet',
1441                 type : 'text/css',
1442                 href : stylesheets
1443             });
1444             
1445             return;
1446         }
1447         var _this = this;
1448      
1449         Roo.each(stylesheets, function(s) {
1450             if(!s.length){
1451                 return;
1452             }
1453             
1454             Roo.get(_this.iframe.contentDocument.head).createChild({
1455                 tag : 'link',
1456                 rel : 'stylesheet',
1457                 type : 'text/css',
1458                 href : s
1459             });
1460         });
1461
1462         
1463     },
1464     
1465     
1466     updateLanguage : function()
1467     {
1468         if (!this.iframe || !this.iframe.contentDocument) {
1469             return;
1470         }
1471         Roo.get(this.iframe.contentDocument.body).attr("lang", this.language);
1472     },
1473     
1474     
1475     removeStylesheets : function()
1476     {
1477         var _this = this;
1478         
1479         Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1480             s.remove();
1481         });
1482     },
1483     
1484     setStyle : function(style)
1485     {
1486         Roo.get(this.iframe.contentDocument.head).createChild({
1487             tag : 'style',
1488             type : 'text/css',
1489             html : style
1490         });
1491
1492         return;
1493     }
1494     
1495     // hide stuff that is not compatible
1496     /**
1497      * @event blur
1498      * @hide
1499      */
1500     /**
1501      * @event change
1502      * @hide
1503      */
1504     /**
1505      * @event focus
1506      * @hide
1507      */
1508     /**
1509      * @event specialkey
1510      * @hide
1511      */
1512     /**
1513      * @cfg {String} fieldClass @hide
1514      */
1515     /**
1516      * @cfg {String} focusClass @hide
1517      */
1518     /**
1519      * @cfg {String} autoCreate @hide
1520      */
1521     /**
1522      * @cfg {String} inputType @hide
1523      */
1524     /**
1525      * @cfg {String} invalidClass @hide
1526      */
1527     /**
1528      * @cfg {String} invalidText @hide
1529      */
1530     /**
1531      * @cfg {String} msgFx @hide
1532      */
1533     /**
1534      * @cfg {String} validateOnBlur @hide
1535      */
1536 });
1537
1538 Roo.HtmlEditorCore.white = [
1539         'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
1540         
1541        'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD',      'DIR',       'DIV', 
1542        'DL',      'DT',         'H1',     'H2',      'H3',        'H4', 
1543        'H5',      'H6',         'HR',     'ISINDEX', 'LISTING',   'MARQUEE', 
1544        'MENU',    'MULTICOL',   'OL',     'P',       'PLAINTEXT', 'PRE', 
1545        'TABLE',   'UL',         'XMP', 
1546        
1547        'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH', 
1548       'THEAD',   'TR', 
1549      
1550       'DIR', 'MENU', 'OL', 'UL', 'DL',
1551        
1552       'EMBED',  'OBJECT'
1553 ];
1554
1555
1556 Roo.HtmlEditorCore.black = [
1557     //    'embed',  'object', // enable - backend responsiblity to clean thiese
1558         'APPLET', // 
1559         'BASE',   'BASEFONT', 'BGSOUND', 'BLINK',  'BODY', 
1560         'FRAME',  'FRAMESET', 'HEAD',    'HTML',   'ILAYER', 
1561         'IFRAME', 'LAYER',  'LINK',     'META',    'OBJECT',   
1562         'SCRIPT', 'STYLE' ,'TITLE',  'XML',
1563         //'FONT' // CLEAN LATER..
1564         'COLGROUP', 'COL'   // messy tables.
1565         
1566         
1567 ];
1568 Roo.HtmlEditorCore.clean = [ // ?? needed???
1569      'SCRIPT', 'STYLE', 'TITLE', 'XML'
1570 ];
1571 Roo.HtmlEditorCore.tag_remove = [
1572     'FONT', 'TBODY'  
1573 ];
1574 // attributes..
1575
1576 Roo.HtmlEditorCore.ablack = [
1577     'on'
1578 ];
1579     
1580 Roo.HtmlEditorCore.aclean = [ 
1581     'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc' 
1582 ];
1583
1584 // protocols..
1585 Roo.HtmlEditorCore.pwhite= [
1586         'http',  'https',  'mailto'
1587 ];
1588
1589 // white listed style attributes.
1590 Roo.HtmlEditorCore.cwhite= [
1591       //  'text-align', /// default is to allow most things..
1592       
1593          
1594 //        'font-size'//??
1595 ];
1596
1597 // black listed style attributes.
1598 Roo.HtmlEditorCore.cblack= [
1599       //  'font-size' -- this can be set by the project 
1600 ];
1601
1602
1603
1604
1605