60aed65b1d201cf2e91e31e30a5f92e7c4278232
[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         Roo.log("HTML EDITOR ON PASTEEE");
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