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