Changed 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                 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         Roo.log('SSSSSSS');
661         Roo.log(html);
662         
663         var d = (new DOMParser().parseFromString(html, 'text/html')).body;
664         
665         
666         var sn = this.getParentElement();
667         // check if d contains a table, and prevent nesting??
668         //Roo.log(d.getElementsByTagName('table'));
669         //Roo.log(sn);
670         //Roo.log(sn.closest('table'));
671         if (d.getElementsByTagName('table').length && sn && sn.closest('table')) {
672             e.preventDefault();
673             this.insertAtCursor("You can not nest tables");
674             //Roo.log("prevent?"); // fixme - 
675             return false;
676         }
677         
678         
679         
680         if (images.length > 0) {
681             // replace all v:imagedata - with img.
682             var ar = Array.from(d.getElementsByTagName('v:imagedata'));
683             Roo.each(ar, function(node) {
684                 node.parentNode.insertBefore(d.ownerDocument.createElement('img'), node );
685                 node.parentNode.removeChild(node);
686             });
687             
688             
689             Roo.each(d.getElementsByTagName('img'), function(img, i) {
690                 img.setAttribute('src', images[i]);
691             });
692         }
693         if (this.autoClean) {
694             new Roo.htmleditor.FilterWord({ node : d });
695             
696             new Roo.htmleditor.FilterStyleToTag({ node : d });
697             new Roo.htmleditor.FilterAttributes({
698                 node : d,
699                 attrib_white : [
700                     'href',
701                     'src',
702                     'name',
703                     'align',
704                     'colspan',
705                     'rowspan' 
706                 /*  THESE ARE NOT ALLWOED FOR PASTE
707                  *    'data-display',
708                     'data-caption-display',
709                     'data-width',
710                     'data-caption',
711                     'start' ,
712                     'style',
713                     // youtube embed.
714                     'class',
715                     'allowfullscreen',
716                     'frameborder',
717                     'width',
718                     'height',
719                     'alt'
720                     */
721                     ],
722                 attrib_clean : ['href', 'src' ] 
723             });
724             new Roo.htmleditor.FilterBlack({ node : d, tag : this.black});
725             // should be fonts..
726             new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT', ':' ]} );
727             new Roo.htmleditor.FilterParagraph({ node : d });
728             new Roo.htmleditor.FilterHashLink({node : d});
729             new Roo.htmleditor.FilterSpan({ node : d });
730             new Roo.htmleditor.FilterLongBr({ node : d });
731             new Roo.htmleditor.FilterComment({ node : d });
732             
733             
734         }
735         if (this.enableBlocks) {
736                 
737             Array.from(d.getElementsByTagName('img')).forEach(function(img) {
738                 if (img.closest('figure')) { // assume!! that it's aready
739                     return;
740                 }
741                 var fig  = new Roo.htmleditor.BlockFigure({
742                     image_src  : img.src
743                 });
744                 fig.updateElement(img); // replace it..
745                 
746             });
747         }
748         
749         
750         this.insertAtCursor(d.innerHTML.replace(/&nbsp;/g,' '));
751         if (this.enableBlocks) {
752             Roo.htmleditor.Block.initAll(this.doc.body);
753         }
754          
755         
756         e.preventDefault();
757         this.owner.fireEvent('paste', this);
758         return false;
759         // default behaveiour should be our local cleanup paste? (optional?)
760         // for simple editor - we want to hammer the paste and get rid of everything... - so over-rideable..
761         //this.owner.fireEvent('paste', e, v);
762     },
763     // private
764     onDestroy : function(){
765         
766         
767         
768         if(this.rendered){
769             
770             //for (var i =0; i < this.toolbars.length;i++) {
771             //    // fixme - ask toolbars for heights?
772             //    this.toolbars[i].onDestroy();
773            // }
774             
775             //this.wrap.dom.innerHTML = '';
776             //this.wrap.remove();
777         }
778     },
779
780     // private
781     onFirstFocus : function(){
782         
783         this.assignDocWin();
784         this.undoManager = new Roo.lib.UndoManager(100,(this.doc.body || this.doc.documentElement));
785         
786         this.activated = true;
787          
788     
789         if(Roo.isGecko){ // prevent silly gecko errors
790             this.win.focus();
791             var s = this.win.getSelection();
792             if(!s.focusNode || s.focusNode.nodeType != 3){
793                 var r = s.getRangeAt(0);
794                 r.selectNodeContents((this.doc.body || this.doc.documentElement));
795                 r.collapse(true);
796                 this.deferFocus();
797             }
798             try{
799                 this.execCmd('useCSS', true);
800                 this.execCmd('styleWithCSS', false);
801             }catch(e){}
802         }
803         this.owner.fireEvent('activate', this);
804     },
805
806     // private
807     adjustFont: function(btn){
808         var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
809         //if(Roo.isSafari){ // safari
810         //    adjust *= 2;
811        // }
812         var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
813         if(Roo.isSafari){ // safari
814             var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
815             v =  (v < 10) ? 10 : v;
816             v =  (v > 48) ? 48 : v;
817             v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
818             
819         }
820         
821         
822         v = Math.max(1, v+adjust);
823         
824         this.execCmd('FontSize', v  );
825     },
826
827     onEditorEvent : function(e)
828     {
829          
830         
831         if (e && (e.ctrlKey || e.metaKey) && e.keyCode === 90) {
832             return; // we do not handle this.. (undo manager does..)
833         }
834         // clicking a 'block'?
835         
836         // in theory this detects if the last element is not a br, then we try and do that.
837         // its so clicking in space at bottom triggers adding a br and moving the cursor.
838         if (e &&
839             e.target.nodeName == 'BODY' &&
840             e.type == "mouseup" &&
841             this.doc.body.lastChild
842            ) {
843             var lc = this.doc.body.lastChild;
844             // gtx-trans is google translate plugin adding crap.
845             while ((lc.nodeType == 3 && lc.nodeValue == '') || lc.id == 'gtx-trans') {
846                 lc = lc.previousSibling;
847             }
848             if (lc.nodeType == 1 && lc.nodeName != 'BR') {
849             // if last element is <BR> - then dont do anything.
850             
851                 var ns = this.doc.createElement('br');
852                 this.doc.body.appendChild(ns);
853                 range = this.doc.createRange();
854                 range.setStartAfter(ns);
855                 range.collapse(true);
856                 var sel = this.win.getSelection();
857                 sel.removeAllRanges();
858                 sel.addRange(range);
859             }
860         }
861         
862         
863         
864         this.fireEditorEvent(e);
865       //  this.updateToolbar();
866         this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
867     },
868     
869     fireEditorEvent: function(e)
870     {
871         this.owner.fireEvent('editorevent', this, e);
872     },
873
874     insertTag : function(tg)
875     {
876         // could be a bit smarter... -> wrap the current selected tRoo..
877         if (tg.toLowerCase() == 'span' ||
878             tg.toLowerCase() == 'code' ||
879             tg.toLowerCase() == 'sup' ||
880             tg.toLowerCase() == 'sub' 
881             ) {
882             
883             range = this.createRange(this.getSelection());
884             var wrappingNode = this.doc.createElement(tg.toLowerCase());
885             wrappingNode.appendChild(range.extractContents());
886             range.insertNode(wrappingNode);
887
888             return;
889             
890             
891             
892         }
893         this.execCmd("formatblock",   tg);
894         this.undoManager.addEvent(); 
895     },
896     
897     insertText : function(txt)
898     {
899         
900         
901         var range = this.createRange();
902         range.deleteContents();
903                //alert(Sender.getAttribute('label'));
904                
905         range.insertNode(this.doc.createTextNode(txt));
906         this.undoManager.addEvent();
907     } ,
908     
909      
910
911     /**
912      * Executes a Midas editor command on the editor document and performs necessary focus and
913      * toolbar updates. <b>This should only be called after the editor is initialized.</b>
914      * @param {String} cmd The Midas command
915      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
916      */
917     relayCmd : function(cmd, value)
918     {
919         
920         switch (cmd) {
921             case 'justifyleft':
922             case 'justifyright':
923             case 'justifycenter':
924                 // if we are in a cell, then we will adjust the
925                 var n = this.getParentElement();
926                 var td = n.closest('td');
927                 if (td) {
928                     var bl = Roo.htmleditor.Block.factory(td);
929                     bl.textAlign = cmd.replace('justify','');
930                     bl.updateElement();
931                     this.owner.fireEvent('editorevent', this);
932                     return;
933                 }
934                 this.execCmd('styleWithCSS', true); // 
935                 break;
936             case 'bold':
937             case 'italic':
938             case 'underline':                
939                 // if there is no selection, then we insert, and set the curson inside it..
940                 this.execCmd('styleWithCSS', false); 
941                 break;
942                 
943         
944             default:
945                 break;
946         }
947         
948         
949         this.win.focus();
950         this.execCmd(cmd, value);
951         this.owner.fireEvent('editorevent', this);
952         //this.updateToolbar();
953         this.owner.deferFocus();
954     },
955
956     /**
957      * Executes a Midas editor command directly on the editor document.
958      * For visual commands, you should use {@link #relayCmd} instead.
959      * <b>This should only be called after the editor is initialized.</b>
960      * @param {String} cmd The Midas command
961      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
962      */
963     execCmd : function(cmd, value){
964         this.doc.execCommand(cmd, false, value === undefined ? null : value);
965         this.syncValue();
966     },
967  
968  
969    
970     /**
971      * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
972      * to insert tRoo.
973      * @param {String} text | dom node.. 
974      */
975     insertAtCursor : function(text)
976     {
977         
978         if(!this.activated){
979             return;
980         }
981          
982         if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
983             this.win.focus();
984             
985             
986             // from jquery ui (MIT licenced)
987             var range, node;
988             var win = this.win;
989             
990             if (win.getSelection && win.getSelection().getRangeAt) {
991                 
992                 // delete the existing?
993                 
994                 this.createRange(this.getSelection()).deleteContents();
995                 range = win.getSelection().getRangeAt(0);
996                 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
997                 range.insertNode(node);
998                 range = range.cloneRange();
999                 range.collapse(false);
1000                  
1001                 win.getSelection().removeAllRanges();
1002                 win.getSelection().addRange(range);
1003                 
1004                 
1005                 
1006             } else if (win.document.selection && win.document.selection.createRange) {
1007                 // no firefox support
1008                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
1009                 win.document.selection.createRange().pasteHTML(txt);
1010             
1011             } else {
1012                 // no firefox support
1013                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
1014                 this.execCmd('InsertHTML', txt);
1015             } 
1016             this.syncValue();
1017             
1018             this.deferFocus();
1019         }
1020     },
1021  // private
1022     mozKeyPress : function(e){
1023         if(e.ctrlKey){
1024             var c = e.getCharCode(), cmd;
1025           
1026             if(c > 0){
1027                 c = String.fromCharCode(c).toLowerCase();
1028                 switch(c){
1029                     case 'b':
1030                         cmd = 'bold';
1031                         break;
1032                     case 'i':
1033                         cmd = 'italic';
1034                         break;
1035                     
1036                     case 'u':
1037                         cmd = 'underline';
1038                         break;
1039                     
1040                     //case 'v':
1041                       //  this.cleanUpPaste.defer(100, this);
1042                       //  return;
1043                         
1044                 }
1045                 if(cmd){
1046                     
1047                     this.relayCmd(cmd);
1048                     //this.win.focus();
1049                     //this.execCmd(cmd);
1050                     //this.deferFocus();
1051                     e.preventDefault();
1052                 }
1053                 
1054             }
1055         }
1056     },
1057
1058     // private
1059     fixKeys : function(){ // load time branching for fastest keydown performance
1060         
1061         
1062         if(Roo.isIE){
1063             return function(e){
1064                 var k = e.getKey(), r;
1065                 if(k == e.TAB){
1066                     e.stopEvent();
1067                     r = this.doc.selection.createRange();
1068                     if(r){
1069                         r.collapse(true);
1070                         r.pasteHTML('&#160;&#160;&#160;&#160;');
1071                         this.deferFocus();
1072                     }
1073                     return;
1074                 }
1075                 /// this is handled by Roo.htmleditor.KeyEnter
1076                  /*
1077                 if(k == e.ENTER){
1078                     r = this.doc.selection.createRange();
1079                     if(r){
1080                         var target = r.parentElement();
1081                         if(!target || target.tagName.toLowerCase() != 'li'){
1082                             e.stopEvent();
1083                             r.pasteHTML('<br/>');
1084                             r.collapse(false);
1085                             r.select();
1086                         }
1087                     }
1088                 }
1089                 */
1090                 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
1091                 //    this.cleanUpPaste.defer(100, this);
1092                 //    return;
1093                 //}
1094                 
1095                 
1096             };
1097         }else if(Roo.isOpera){
1098             return function(e){
1099                 var k = e.getKey();
1100                 if(k == e.TAB){
1101                     e.stopEvent();
1102                     this.win.focus();
1103                     this.execCmd('InsertHTML','&#160;&#160;&#160;&#160;');
1104                     this.deferFocus();
1105                 }
1106                
1107                 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
1108                 //    this.cleanUpPaste.defer(100, this);
1109                  //   return;
1110                 //}
1111                 
1112             };
1113         }else if(Roo.isSafari){
1114             return function(e){
1115                 var k = e.getKey();
1116                 
1117                 if(k == e.TAB){
1118                     e.stopEvent();
1119                     this.execCmd('InsertText','\t');
1120                     this.deferFocus();
1121                     return;
1122                 }
1123                  this.mozKeyPress(e);
1124                 
1125                //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
1126                  //   this.cleanUpPaste.defer(100, this);
1127                  //   return;
1128                // }
1129                 
1130              };
1131         }
1132     }(),
1133     
1134     getAllAncestors: function()
1135     {
1136         var p = this.getSelectedNode();
1137         var a = [];
1138         if (!p) {
1139             a.push(p); // push blank onto stack..
1140             p = this.getParentElement();
1141         }
1142         
1143         
1144         while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
1145             a.push(p);
1146             p = p.parentNode;
1147         }
1148         a.push(this.doc.body);
1149         return a;
1150     },
1151     lastSel : false,
1152     lastSelNode : false,
1153     
1154     
1155     getSelection : function() 
1156     {
1157         this.assignDocWin();
1158         return Roo.lib.Selection.wrap(Roo.isIE ? this.doc.selection : this.win.getSelection(), this.doc);
1159     },
1160     /**
1161      * Select a dom node
1162      * @param {DomElement} node the node to select
1163      */
1164     selectNode : function(node, collapse)
1165     {
1166         var nodeRange = node.ownerDocument.createRange();
1167         try {
1168             nodeRange.selectNode(node);
1169         } catch (e) {
1170             nodeRange.selectNodeContents(node);
1171         }
1172         if (collapse === true) {
1173             nodeRange.collapse(true);
1174         }
1175         //
1176         var s = this.win.getSelection();
1177         s.removeAllRanges();
1178         s.addRange(nodeRange);
1179     },
1180     
1181     getSelectedNode: function() 
1182     {
1183         // this may only work on Gecko!!!
1184         
1185         // should we cache this!!!!
1186         
1187          
1188          
1189         var range = this.createRange(this.getSelection()).cloneRange();
1190         
1191         if (Roo.isIE) {
1192             var parent = range.parentElement();
1193             while (true) {
1194                 var testRange = range.duplicate();
1195                 testRange.moveToElementText(parent);
1196                 if (testRange.inRange(range)) {
1197                     break;
1198                 }
1199                 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
1200                     break;
1201                 }
1202                 parent = parent.parentElement;
1203             }
1204             return parent;
1205         }
1206         
1207         // is ancestor a text element.
1208         var ac =  range.commonAncestorContainer;
1209         if (ac.nodeType == 3) {
1210             ac = ac.parentNode;
1211         }
1212         
1213         var ar = ac.childNodes;
1214          
1215         var nodes = [];
1216         var other_nodes = [];
1217         var has_other_nodes = false;
1218         for (var i=0;i<ar.length;i++) {
1219             if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ? 
1220                 continue;
1221             }
1222             // fullly contained node.
1223             
1224             if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
1225                 nodes.push(ar[i]);
1226                 continue;
1227             }
1228             
1229             // probably selected..
1230             if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
1231                 other_nodes.push(ar[i]);
1232                 continue;
1233             }
1234             // outer..
1235             if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0))  {
1236                 continue;
1237             }
1238             
1239             
1240             has_other_nodes = true;
1241         }
1242         if (!nodes.length && other_nodes.length) {
1243             nodes= other_nodes;
1244         }
1245         if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
1246             return false;
1247         }
1248         
1249         return nodes[0];
1250     },
1251     
1252     
1253     createRange: function(sel)
1254     {
1255         // this has strange effects when using with 
1256         // top toolbar - not sure if it's a great idea.
1257         //this.editor.contentWindow.focus();
1258         if (typeof sel != "undefined") {
1259             try {
1260                 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
1261             } catch(e) {
1262                 return this.doc.createRange();
1263             }
1264         } else {
1265             return this.doc.createRange();
1266         }
1267     },
1268     getParentElement: function()
1269     {
1270         
1271         this.assignDocWin();
1272         var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
1273         
1274         var range = this.createRange(sel);
1275          
1276         try {
1277             var p = range.commonAncestorContainer;
1278             while (p.nodeType == 3) { // text node
1279                 p = p.parentNode;
1280             }
1281             return p;
1282         } catch (e) {
1283             return null;
1284         }
1285     
1286     },
1287     /***
1288      *
1289      * Range intersection.. the hard stuff...
1290      *  '-1' = before
1291      *  '0' = hits..
1292      *  '1' = after.
1293      *         [ -- selected range --- ]
1294      *   [fail]                        [fail]
1295      *
1296      *    basically..
1297      *      if end is before start or  hits it. fail.
1298      *      if start is after end or hits it fail.
1299      *
1300      *   if either hits (but other is outside. - then it's not 
1301      *   
1302      *    
1303      **/
1304     
1305     
1306     // @see http://www.thismuchiknow.co.uk/?p=64.
1307     rangeIntersectsNode : function(range, node)
1308     {
1309         var nodeRange = node.ownerDocument.createRange();
1310         try {
1311             nodeRange.selectNode(node);
1312         } catch (e) {
1313             nodeRange.selectNodeContents(node);
1314         }
1315     
1316         var rangeStartRange = range.cloneRange();
1317         rangeStartRange.collapse(true);
1318     
1319         var rangeEndRange = range.cloneRange();
1320         rangeEndRange.collapse(false);
1321     
1322         var nodeStartRange = nodeRange.cloneRange();
1323         nodeStartRange.collapse(true);
1324     
1325         var nodeEndRange = nodeRange.cloneRange();
1326         nodeEndRange.collapse(false);
1327     
1328         return rangeStartRange.compareBoundaryPoints(
1329                  Range.START_TO_START, nodeEndRange) == -1 &&
1330                rangeEndRange.compareBoundaryPoints(
1331                  Range.START_TO_START, nodeStartRange) == 1;
1332         
1333          
1334     },
1335     rangeCompareNode : function(range, node)
1336     {
1337         var nodeRange = node.ownerDocument.createRange();
1338         try {
1339             nodeRange.selectNode(node);
1340         } catch (e) {
1341             nodeRange.selectNodeContents(node);
1342         }
1343         
1344         
1345         range.collapse(true);
1346     
1347         nodeRange.collapse(true);
1348      
1349         var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1350         var ee = range.compareBoundaryPoints(  Range.END_TO_END, nodeRange);
1351          
1352         //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1353         
1354         var nodeIsBefore   =  ss == 1;
1355         var nodeIsAfter    = ee == -1;
1356         
1357         if (nodeIsBefore && nodeIsAfter) {
1358             return 0; // outer
1359         }
1360         if (!nodeIsBefore && nodeIsAfter) {
1361             return 1; //right trailed.
1362         }
1363         
1364         if (nodeIsBefore && !nodeIsAfter) {
1365             return 2;  // left trailed.
1366         }
1367         // fully contined.
1368         return 3;
1369     },
1370  
1371     cleanWordChars : function(input) {// change the chars to hex code
1372         
1373        var swapCodes  = [ 
1374             [    8211, "&#8211;" ], 
1375             [    8212, "&#8212;" ], 
1376             [    8216,  "'" ],  
1377             [    8217, "'" ],  
1378             [    8220, '"' ],  
1379             [    8221, '"' ],  
1380             [    8226, "*" ],  
1381             [    8230, "..." ]
1382         ]; 
1383         var output = input;
1384         Roo.each(swapCodes, function(sw) { 
1385             var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1386             
1387             output = output.replace(swapper, sw[1]);
1388         });
1389         
1390         return output;
1391     },
1392     
1393      
1394     
1395         
1396     
1397     cleanUpChild : function (node)
1398     {
1399         
1400         new Roo.htmleditor.FilterComment({node : node});
1401         new Roo.htmleditor.FilterAttributes({
1402                 node : node,
1403                 attrib_black : this.ablack,
1404                 attrib_clean : this.aclean,
1405                 style_white : this.cwhite,
1406                 style_black : this.cblack
1407         });
1408         new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
1409         new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
1410          
1411         
1412     },
1413     
1414     /**
1415      * Clean up MS wordisms...
1416      * @deprecated - use filter directly
1417      */
1418     cleanWord : function(node)
1419     {
1420         new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
1421         new Roo.htmleditor.FilterKeepChildren({node : node ? node : this.doc.body, tag : [ 'FONT', ':' ]} );
1422         
1423     },
1424    
1425     
1426     /**
1427
1428      * @deprecated - use filters
1429      */
1430     cleanTableWidths : function(node)
1431     {
1432         new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
1433         
1434  
1435     },
1436     
1437      
1438         
1439     applyBlacklists : function()
1440     {
1441         var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white  : [];
1442         var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black :  [];
1443         
1444         this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean :  Roo.HtmlEditorCore.aclean;
1445         this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack :  Roo.HtmlEditorCore.ablack;
1446         this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove :  Roo.HtmlEditorCore.tag_remove;
1447         
1448         this.white = [];
1449         this.black = [];
1450         Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1451             if (b.indexOf(tag) > -1) {
1452                 return;
1453             }
1454             this.white.push(tag);
1455             
1456         }, this);
1457         
1458         Roo.each(w, function(tag) {
1459             if (b.indexOf(tag) > -1) {
1460                 return;
1461             }
1462             if (this.white.indexOf(tag) > -1) {
1463                 return;
1464             }
1465             this.white.push(tag);
1466             
1467         }, this);
1468         
1469         
1470         Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1471             if (w.indexOf(tag) > -1) {
1472                 return;
1473             }
1474             this.black.push(tag);
1475             
1476         }, this);
1477         
1478         Roo.each(b, function(tag) {
1479             if (w.indexOf(tag) > -1) {
1480                 return;
1481             }
1482             if (this.black.indexOf(tag) > -1) {
1483                 return;
1484             }
1485             this.black.push(tag);
1486             
1487         }, this);
1488         
1489         
1490         w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite  : [];
1491         b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack :  [];
1492         
1493         this.cwhite = [];
1494         this.cblack = [];
1495         Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1496             if (b.indexOf(tag) > -1) {
1497                 return;
1498             }
1499             this.cwhite.push(tag);
1500             
1501         }, this);
1502         
1503         Roo.each(w, function(tag) {
1504             if (b.indexOf(tag) > -1) {
1505                 return;
1506             }
1507             if (this.cwhite.indexOf(tag) > -1) {
1508                 return;
1509             }
1510             this.cwhite.push(tag);
1511             
1512         }, this);
1513         
1514         
1515         Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1516             if (w.indexOf(tag) > -1) {
1517                 return;
1518             }
1519             this.cblack.push(tag);
1520             
1521         }, this);
1522         
1523         Roo.each(b, function(tag) {
1524             if (w.indexOf(tag) > -1) {
1525                 return;
1526             }
1527             if (this.cblack.indexOf(tag) > -1) {
1528                 return;
1529             }
1530             this.cblack.push(tag);
1531             
1532         }, this);
1533     },
1534     
1535     setStylesheets : function(stylesheets)
1536     {
1537         if(typeof(stylesheets) == 'string'){
1538             Roo.get(this.iframe.contentDocument.head).createChild({
1539                 tag : 'link',
1540                 rel : 'stylesheet',
1541                 type : 'text/css',
1542                 href : stylesheets
1543             });
1544             
1545             return;
1546         }
1547         var _this = this;
1548      
1549         Roo.each(stylesheets, function(s) {
1550             if(!s.length){
1551                 return;
1552             }
1553             
1554             Roo.get(_this.iframe.contentDocument.head).createChild({
1555                 tag : 'link',
1556                 rel : 'stylesheet',
1557                 type : 'text/css',
1558                 href : s
1559             });
1560         });
1561
1562         
1563     },
1564     
1565     
1566     updateLanguage : function()
1567     {
1568         if (!this.iframe || !this.iframe.contentDocument) {
1569             return;
1570         }
1571         Roo.get(this.iframe.contentDocument.body).attr("lang", this.language);
1572     },
1573     
1574     
1575     removeStylesheets : function()
1576     {
1577         var _this = this;
1578         
1579         Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1580             s.remove();
1581         });
1582     },
1583     
1584     setStyle : function(style)
1585     {
1586         Roo.get(this.iframe.contentDocument.head).createChild({
1587             tag : 'style',
1588             type : 'text/css',
1589             html : style
1590         });
1591
1592         return;
1593     }
1594     
1595     // hide stuff that is not compatible
1596     /**
1597      * @event blur
1598      * @hide
1599      */
1600     /**
1601      * @event change
1602      * @hide
1603      */
1604     /**
1605      * @event focus
1606      * @hide
1607      */
1608     /**
1609      * @event specialkey
1610      * @hide
1611      */
1612     /**
1613      * @cfg {String} fieldClass @hide
1614      */
1615     /**
1616      * @cfg {String} focusClass @hide
1617      */
1618     /**
1619      * @cfg {String} autoCreate @hide
1620      */
1621     /**
1622      * @cfg {String} inputType @hide
1623      */
1624     /**
1625      * @cfg {String} invalidClass @hide
1626      */
1627     /**
1628      * @cfg {String} invalidText @hide
1629      */
1630     /**
1631      * @cfg {String} msgFx @hide
1632      */
1633     /**
1634      * @cfg {String} validateOnBlur @hide
1635      */
1636 });
1637
1638 Roo.HtmlEditorCore.white = [
1639         'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
1640         
1641        'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD',      'DIR',       'DIV', 
1642        'DL',      'DT',         'H1',     'H2',      'H3',        'H4', 
1643        'H5',      'H6',         'HR',     'ISINDEX', 'LISTING',   'MARQUEE', 
1644        'MENU',    'MULTICOL',   'OL',     'P',       'PLAINTEXT', 'PRE', 
1645        'TABLE',   'UL',         'XMP', 
1646        
1647        'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH', 
1648       'THEAD',   'TR', 
1649      
1650       'DIR', 'MENU', 'OL', 'UL', 'DL',
1651        
1652       'EMBED',  'OBJECT'
1653 ];
1654
1655
1656 Roo.HtmlEditorCore.black = [
1657     //    'embed',  'object', // enable - backend responsiblity to clean thiese
1658         'APPLET', // 
1659         'BASE',   'BASEFONT', 'BGSOUND', 'BLINK',  'BODY', 
1660         'FRAME',  'FRAMESET', 'HEAD',    'HTML',   'ILAYER', 
1661         'IFRAME', 'LAYER',  'LINK',     'META',    'OBJECT',   
1662         'SCRIPT', 'STYLE' ,'TITLE',  'XML',
1663         //'FONT' // CLEAN LATER..
1664         'COLGROUP', 'COL'   // messy tables.
1665         
1666         
1667 ];
1668 Roo.HtmlEditorCore.clean = [ // ?? needed???
1669      'SCRIPT', 'STYLE', 'TITLE', 'XML'
1670 ];
1671 Roo.HtmlEditorCore.tag_remove = [
1672     'FONT', 'TBODY'  
1673 ];
1674 // attributes..
1675
1676 Roo.HtmlEditorCore.ablack = [
1677     'on'
1678 ];
1679     
1680 Roo.HtmlEditorCore.aclean = [ 
1681     'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc' 
1682 ];
1683
1684 // protocols..
1685 Roo.HtmlEditorCore.pwhite= [
1686         'http',  'https',  'mailto'
1687 ];
1688
1689 // white listed style attributes.
1690 Roo.HtmlEditorCore.cwhite= [
1691       //  'text-align', /// default is to allow most things..
1692       
1693          
1694 //        'font-size'//??
1695 ];
1696
1697 // black listed style attributes.
1698 Roo.HtmlEditorCore.cblack= [
1699       //  'font-size' -- this can be set by the project 
1700 ];
1701
1702
1703
1704
1705