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