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