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