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