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