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