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