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