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