Fix #6901 - CRM - allow comments in HTML for IE
[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     // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
79     
80     // defaults : white / black...
81     this.applyBlacklists();
82     
83     
84     
85 };
86
87
88 Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
89
90
91      /**
92      * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field 
93      */
94     
95     owner : false,
96     
97      /**
98      * @cfg {String} resizable  's' or 'se' or 'e' - wrapps the element in a
99      *                        Roo.resizable.
100      */
101     resizable : 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     /**
112      * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
113      * 
114      */
115     stylesheets: false,
116     
117     /**
118      * @cfg {boolean} allowComments - default false - allow comments in HTML source - by default they are stripped - if you are editing email you may need this.
119      */
120     
121     allowComments: false,
122     // id of frame..
123     frameId: false,
124     
125     // private properties
126     validationEvent : false,
127     deferHeight: true,
128     initialized : false,
129     activated : false,
130     sourceEditMode : false,
131     onFocus : Roo.emptyFn,
132     iframePad:3,
133     hideMode:'offsets',
134     
135     clearUp: true,
136     
137     // blacklist + whitelisted elements..
138     black: false,
139     white: false,
140      
141     bodyCls : '',
142
143     /**
144      * Protected method that will not generally be called directly. It
145      * is called when the editor initializes the iframe with HTML contents. Override this method if you
146      * want to change the initialization markup of the iframe (e.g. to add stylesheets).
147      */
148     getDocMarkup : function(){
149         // body styles..
150         var st = '';
151         
152         // inherit styels from page...?? 
153         if (this.stylesheets === false) {
154             
155             Roo.get(document.head).select('style').each(function(node) {
156                 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
157             });
158             
159             Roo.get(document.head).select('link').each(function(node) { 
160                 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
161             });
162             
163         } else if (!this.stylesheets.length) {
164                 // simple..
165                 st = '<style type="text/css">' +
166                     'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
167                    '</style>';
168         } else {
169             for (var i in this.stylesheets) { 
170                 st += '<link rel="stylesheet" href="' + this.stylesheets[i] +'" type="text/css">';
171             }
172             
173         }
174         
175         st +=  '<style type="text/css">' +
176             'IMG { cursor: pointer } ' +
177         '</style>';
178
179         var cls = 'roo-htmleditor-body';
180         
181         if(this.bodyCls.length){
182             cls += ' ' + this.bodyCls;
183         }
184         
185         return '<html><head>' + st  +
186             //<style type="text/css">' +
187             //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
188             //'</style>' +
189             ' </head><body contenteditable="true" data-enable-grammerly="true" class="' +  cls + '"></body></html>';
190     },
191
192     // private
193     onRender : function(ct, position)
194     {
195         var _t = this;
196         //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
197         this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
198         
199         
200         this.el.dom.style.border = '0 none';
201         this.el.dom.setAttribute('tabIndex', -1);
202         this.el.addClass('x-hidden hide');
203         
204         
205         
206         if(Roo.isIE){ // fix IE 1px bogus margin
207             this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
208         }
209        
210         
211         this.frameId = Roo.id();
212         
213          
214         
215         var iframe = this.owner.wrap.createChild({
216             tag: 'iframe',
217             cls: 'form-control', // bootstrap..
218             id: this.frameId,
219             name: this.frameId,
220             frameBorder : 'no',
221             'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL  :  "javascript:false"
222         }, this.el
223         );
224         
225         
226         this.iframe = iframe.dom;
227
228          this.assignDocWin();
229         
230         this.doc.designMode = 'on';
231        
232         this.doc.open();
233         this.doc.write(this.getDocMarkup());
234         this.doc.close();
235
236         
237         var task = { // must defer to wait for browser to be ready
238             run : function(){
239                 //console.log("run task?" + this.doc.readyState);
240                 this.assignDocWin();
241                 if(this.doc.body || this.doc.readyState == 'complete'){
242                     try {
243                         this.doc.designMode="on";
244                     } catch (e) {
245                         return;
246                     }
247                     Roo.TaskMgr.stop(task);
248                     this.initEditor.defer(10, this);
249                 }
250             },
251             interval : 10,
252             duration: 10000,
253             scope: this
254         };
255         Roo.TaskMgr.start(task);
256
257     },
258
259     // private
260     onResize : function(w, h)
261     {
262          Roo.log('resize: ' +w + ',' + h );
263         //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
264         if(!this.iframe){
265             return;
266         }
267         if(typeof w == 'number'){
268             
269             this.iframe.style.width = w + 'px';
270         }
271         if(typeof h == 'number'){
272             
273             this.iframe.style.height = h + 'px';
274             if(this.doc){
275                 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
276             }
277         }
278         
279     },
280
281     /**
282      * Toggles the editor between standard and source edit mode.
283      * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
284      */
285     toggleSourceEdit : function(sourceEditMode){
286         
287         this.sourceEditMode = sourceEditMode === true;
288         
289         if(this.sourceEditMode){
290  
291             Roo.get(this.iframe).addClass(['x-hidden','hide']);     //FIXME - what's the BS styles for these
292             
293         }else{
294             Roo.get(this.iframe).removeClass(['x-hidden','hide']);
295             //this.iframe.className = '';
296             this.deferFocus();
297         }
298         //this.setSize(this.owner.wrap.getSize());
299         //this.fireEvent('editmodechange', this, this.sourceEditMode);
300     },
301
302     
303   
304
305     /**
306      * Protected method that will not generally be called directly. If you need/want
307      * custom HTML cleanup, this is the method you should override.
308      * @param {String} html The HTML to be cleaned
309      * return {String} The cleaned HTML
310      */
311     cleanHtml : function(html){
312         html = String(html);
313         if(html.length > 5){
314             if(Roo.isSafari){ // strip safari nonsense
315                 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
316             }
317         }
318         if(html == '&nbsp;'){
319             html = '';
320         }
321         return html;
322     },
323
324     /**
325      * HTML Editor -> Textarea
326      * Protected method that will not generally be called directly. Syncs the contents
327      * of the editor iframe with the textarea.
328      */
329     syncValue : function(){
330         if(this.initialized){
331             var bd = (this.doc.body || this.doc.documentElement);
332             //this.cleanUpPaste(); -- this is done else where and causes havoc..
333             var html = bd.innerHTML;
334             if(Roo.isSafari){
335                 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
336                 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
337                 if(m && m[1]){
338                     html = '<div style="'+m[0]+'">' + html + '</div>';
339                 }
340             }
341             html = this.cleanHtml(html);
342             // fix up the special chars.. normaly like back quotes in word...
343             // however we do not want to do this with chinese..
344             html = html.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u0080-\uFFFF]/g, function(match) {
345                 
346                 var cc = match.charCodeAt();
347
348                 // Get the character value, handling surrogate pairs
349                 if (match.length == 2) {
350                     // It's a surrogate pair, calculate the Unicode code point
351                     var high = match.charCodeAt(0) - 0xD800;
352                     var low  = match.charCodeAt(1) - 0xDC00;
353                     cc = (high * 0x400) + low + 0x10000;
354                 }  else if (
355                     (cc >= 0x4E00 && cc < 0xA000 ) ||
356                     (cc >= 0x3400 && cc < 0x4E00 ) ||
357                     (cc >= 0xf900 && cc < 0xfb00 )
358                 ) {
359                         return match;
360                 }  
361          
362                 // No, use a numeric entity. Here we brazenly (and possibly mistakenly)
363                 return "&#" + cc + ";";
364                 
365                 
366             });
367             
368             
369              
370             if(this.owner.fireEvent('beforesync', this, html) !== false){
371                 this.el.dom.value = html;
372                 this.owner.fireEvent('sync', this, html);
373             }
374         }
375     },
376
377     /**
378      * Protected method that will not generally be called directly. Pushes the value of the textarea
379      * into the iframe editor.
380      */
381     pushValue : function(){
382         if(this.initialized){
383             var v = this.el.dom.value.trim();
384             
385 //            if(v.length < 1){
386 //                v = '&#160;';
387 //            }
388             
389             if(this.owner.fireEvent('beforepush', this, v) !== false){
390                 var d = (this.doc.body || this.doc.documentElement);
391                 d.innerHTML = v;
392                 this.cleanUpPaste();
393                 this.el.dom.value = d.innerHTML;
394                 this.owner.fireEvent('push', this, v);
395             }
396         }
397     },
398
399     // private
400     deferFocus : function(){
401         this.focus.defer(10, this);
402     },
403
404     // doc'ed in Field
405     focus : function(){
406         if(this.win && !this.sourceEditMode){
407             this.win.focus();
408         }else{
409             this.el.focus();
410         }
411     },
412     
413     assignDocWin: function()
414     {
415         var iframe = this.iframe;
416         
417          if(Roo.isIE){
418             this.doc = iframe.contentWindow.document;
419             this.win = iframe.contentWindow;
420         } else {
421 //            if (!Roo.get(this.frameId)) {
422 //                return;
423 //            }
424 //            this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
425 //            this.win = Roo.get(this.frameId).dom.contentWindow;
426             
427             if (!Roo.get(this.frameId) && !iframe.contentDocument) {
428                 return;
429             }
430             
431             this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
432             this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
433         }
434     },
435     
436     // private
437     initEditor : function(){
438         //console.log("INIT EDITOR");
439         this.assignDocWin();
440         
441         
442         
443         this.doc.designMode="on";
444         this.doc.open();
445         this.doc.write(this.getDocMarkup());
446         this.doc.close();
447         
448         var dbody = (this.doc.body || this.doc.documentElement);
449         //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
450         // this copies styles from the containing element into thsi one..
451         // not sure why we need all of this..
452         //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
453         
454         //var ss = this.el.getStyles( 'background-image', 'background-repeat');
455         //ss['background-attachment'] = 'fixed'; // w3c
456         dbody.bgProperties = 'fixed'; // ie
457         //Roo.DomHelper.applyStyles(dbody, ss);
458         Roo.EventManager.on(this.doc, {
459             //'mousedown': this.onEditorEvent,
460             'mouseup': this.onEditorEvent,
461             'dblclick': this.onEditorEvent,
462             'click': this.onEditorEvent,
463             'keyup': this.onEditorEvent,
464             buffer:100,
465             scope: this
466         });
467         if(Roo.isGecko){
468             Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
469         }
470         if(Roo.isIE || Roo.isSafari || Roo.isOpera){
471             Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
472         }
473         this.initialized = true;
474
475         this.owner.fireEvent('initialize', this);
476         this.pushValue();
477     },
478
479     // private
480     onDestroy : function(){
481         
482         
483         
484         if(this.rendered){
485             
486             //for (var i =0; i < this.toolbars.length;i++) {
487             //    // fixme - ask toolbars for heights?
488             //    this.toolbars[i].onDestroy();
489            // }
490             
491             //this.wrap.dom.innerHTML = '';
492             //this.wrap.remove();
493         }
494     },
495
496     // private
497     onFirstFocus : function(){
498         
499         this.assignDocWin();
500         
501         
502         this.activated = true;
503          
504     
505         if(Roo.isGecko){ // prevent silly gecko errors
506             this.win.focus();
507             var s = this.win.getSelection();
508             if(!s.focusNode || s.focusNode.nodeType != 3){
509                 var r = s.getRangeAt(0);
510                 r.selectNodeContents((this.doc.body || this.doc.documentElement));
511                 r.collapse(true);
512                 this.deferFocus();
513             }
514             try{
515                 this.execCmd('useCSS', true);
516                 this.execCmd('styleWithCSS', false);
517             }catch(e){}
518         }
519         this.owner.fireEvent('activate', this);
520     },
521
522     // private
523     adjustFont: function(btn){
524         var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
525         //if(Roo.isSafari){ // safari
526         //    adjust *= 2;
527        // }
528         var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
529         if(Roo.isSafari){ // safari
530             var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
531             v =  (v < 10) ? 10 : v;
532             v =  (v > 48) ? 48 : v;
533             v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
534             
535         }
536         
537         
538         v = Math.max(1, v+adjust);
539         
540         this.execCmd('FontSize', v  );
541     },
542
543     onEditorEvent : function(e)
544     {
545         this.owner.fireEvent('editorevent', this, e);
546       //  this.updateToolbar();
547         this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
548     },
549
550     insertTag : function(tg)
551     {
552         // could be a bit smarter... -> wrap the current selected tRoo..
553         if (tg.toLowerCase() == 'span' ||
554             tg.toLowerCase() == 'code' ||
555             tg.toLowerCase() == 'sup' ||
556             tg.toLowerCase() == 'sub' 
557             ) {
558             
559             range = this.createRange(this.getSelection());
560             var wrappingNode = this.doc.createElement(tg.toLowerCase());
561             wrappingNode.appendChild(range.extractContents());
562             range.insertNode(wrappingNode);
563
564             return;
565             
566             
567             
568         }
569         this.execCmd("formatblock",   tg);
570         
571     },
572     
573     insertText : function(txt)
574     {
575         
576         
577         var range = this.createRange();
578         range.deleteContents();
579                //alert(Sender.getAttribute('label'));
580                
581         range.insertNode(this.doc.createTextNode(txt));
582     } ,
583     
584      
585
586     /**
587      * Executes a Midas editor command on the editor document and performs necessary focus and
588      * toolbar updates. <b>This should only be called after the editor is initialized.</b>
589      * @param {String} cmd The Midas command
590      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
591      */
592     relayCmd : function(cmd, value){
593         this.win.focus();
594         this.execCmd(cmd, value);
595         this.owner.fireEvent('editorevent', this);
596         //this.updateToolbar();
597         this.owner.deferFocus();
598     },
599
600     /**
601      * Executes a Midas editor command directly on the editor document.
602      * For visual commands, you should use {@link #relayCmd} instead.
603      * <b>This should only be called after the editor is initialized.</b>
604      * @param {String} cmd The Midas command
605      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
606      */
607     execCmd : function(cmd, value){
608         this.doc.execCommand(cmd, false, value === undefined ? null : value);
609         this.syncValue();
610     },
611  
612  
613    
614     /**
615      * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
616      * to insert tRoo.
617      * @param {String} text | dom node.. 
618      */
619     insertAtCursor : function(text)
620     {
621         
622         if(!this.activated){
623             return;
624         }
625         /*
626         if(Roo.isIE){
627             this.win.focus();
628             var r = this.doc.selection.createRange();
629             if(r){
630                 r.collapse(true);
631                 r.pasteHTML(text);
632                 this.syncValue();
633                 this.deferFocus();
634             
635             }
636             return;
637         }
638         */
639         if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
640             this.win.focus();
641             
642             
643             // from jquery ui (MIT licenced)
644             var range, node;
645             var win = this.win;
646             
647             if (win.getSelection && win.getSelection().getRangeAt) {
648                 range = win.getSelection().getRangeAt(0);
649                 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
650                 range.insertNode(node);
651             } else if (win.document.selection && win.document.selection.createRange) {
652                 // no firefox support
653                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
654                 win.document.selection.createRange().pasteHTML(txt);
655             } else {
656                 // no firefox support
657                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
658                 this.execCmd('InsertHTML', txt);
659             } 
660             
661             this.syncValue();
662             
663             this.deferFocus();
664         }
665     },
666  // private
667     mozKeyPress : function(e){
668         if(e.ctrlKey){
669             var c = e.getCharCode(), cmd;
670           
671             if(c > 0){
672                 c = String.fromCharCode(c).toLowerCase();
673                 switch(c){
674                     case 'b':
675                         cmd = 'bold';
676                         break;
677                     case 'i':
678                         cmd = 'italic';
679                         break;
680                     
681                     case 'u':
682                         cmd = 'underline';
683                         break;
684                     
685                     case 'v':
686                         this.cleanUpPaste.defer(100, this);
687                         return;
688                         
689                 }
690                 if(cmd){
691                     this.win.focus();
692                     this.execCmd(cmd);
693                     this.deferFocus();
694                     e.preventDefault();
695                 }
696                 
697             }
698         }
699     },
700
701     // private
702     fixKeys : function(){ // load time branching for fastest keydown performance
703         if(Roo.isIE){
704             return function(e){
705                 var k = e.getKey(), r;
706                 if(k == e.TAB){
707                     e.stopEvent();
708                     r = this.doc.selection.createRange();
709                     if(r){
710                         r.collapse(true);
711                         r.pasteHTML('&#160;&#160;&#160;&#160;');
712                         this.deferFocus();
713                     }
714                     return;
715                 }
716                 
717                 if(k == e.ENTER){
718                     r = this.doc.selection.createRange();
719                     if(r){
720                         var target = r.parentElement();
721                         if(!target || target.tagName.toLowerCase() != 'li'){
722                             e.stopEvent();
723                             r.pasteHTML('<br />');
724                             r.collapse(false);
725                             r.select();
726                         }
727                     }
728                 }
729                 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
730                     this.cleanUpPaste.defer(100, this);
731                     return;
732                 }
733                 
734                 
735             };
736         }else if(Roo.isOpera){
737             return function(e){
738                 var k = e.getKey();
739                 if(k == e.TAB){
740                     e.stopEvent();
741                     this.win.focus();
742                     this.execCmd('InsertHTML','&#160;&#160;&#160;&#160;');
743                     this.deferFocus();
744                 }
745                 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
746                     this.cleanUpPaste.defer(100, this);
747                     return;
748                 }
749                 
750             };
751         }else if(Roo.isSafari){
752             return function(e){
753                 var k = e.getKey();
754                 
755                 if(k == e.TAB){
756                     e.stopEvent();
757                     this.execCmd('InsertText','\t');
758                     this.deferFocus();
759                     return;
760                 }
761                if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
762                     this.cleanUpPaste.defer(100, this);
763                     return;
764                 }
765                 
766              };
767         }
768     }(),
769     
770     getAllAncestors: function()
771     {
772         var p = this.getSelectedNode();
773         var a = [];
774         if (!p) {
775             a.push(p); // push blank onto stack..
776             p = this.getParentElement();
777         }
778         
779         
780         while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
781             a.push(p);
782             p = p.parentNode;
783         }
784         a.push(this.doc.body);
785         return a;
786     },
787     lastSel : false,
788     lastSelNode : false,
789     
790     
791     getSelection : function() 
792     {
793         this.assignDocWin();
794         return Roo.isIE ? this.doc.selection : this.win.getSelection();
795     },
796     
797     getSelectedNode: function() 
798     {
799         // this may only work on Gecko!!!
800         
801         // should we cache this!!!!
802         
803         
804         
805          
806         var range = this.createRange(this.getSelection()).cloneRange();
807         
808         if (Roo.isIE) {
809             var parent = range.parentElement();
810             while (true) {
811                 var testRange = range.duplicate();
812                 testRange.moveToElementText(parent);
813                 if (testRange.inRange(range)) {
814                     break;
815                 }
816                 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
817                     break;
818                 }
819                 parent = parent.parentElement;
820             }
821             return parent;
822         }
823         
824         // is ancestor a text element.
825         var ac =  range.commonAncestorContainer;
826         if (ac.nodeType == 3) {
827             ac = ac.parentNode;
828         }
829         
830         var ar = ac.childNodes;
831          
832         var nodes = [];
833         var other_nodes = [];
834         var has_other_nodes = false;
835         for (var i=0;i<ar.length;i++) {
836             if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ? 
837                 continue;
838             }
839             // fullly contained node.
840             
841             if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
842                 nodes.push(ar[i]);
843                 continue;
844             }
845             
846             // probably selected..
847             if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
848                 other_nodes.push(ar[i]);
849                 continue;
850             }
851             // outer..
852             if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0))  {
853                 continue;
854             }
855             
856             
857             has_other_nodes = true;
858         }
859         if (!nodes.length && other_nodes.length) {
860             nodes= other_nodes;
861         }
862         if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
863             return false;
864         }
865         
866         return nodes[0];
867     },
868     createRange: function(sel)
869     {
870         // this has strange effects when using with 
871         // top toolbar - not sure if it's a great idea.
872         //this.editor.contentWindow.focus();
873         if (typeof sel != "undefined") {
874             try {
875                 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
876             } catch(e) {
877                 return this.doc.createRange();
878             }
879         } else {
880             return this.doc.createRange();
881         }
882     },
883     getParentElement: function()
884     {
885         
886         this.assignDocWin();
887         var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
888         
889         var range = this.createRange(sel);
890          
891         try {
892             var p = range.commonAncestorContainer;
893             while (p.nodeType == 3) { // text node
894                 p = p.parentNode;
895             }
896             return p;
897         } catch (e) {
898             return null;
899         }
900     
901     },
902     /***
903      *
904      * Range intersection.. the hard stuff...
905      *  '-1' = before
906      *  '0' = hits..
907      *  '1' = after.
908      *         [ -- selected range --- ]
909      *   [fail]                        [fail]
910      *
911      *    basically..
912      *      if end is before start or  hits it. fail.
913      *      if start is after end or hits it fail.
914      *
915      *   if either hits (but other is outside. - then it's not 
916      *   
917      *    
918      **/
919     
920     
921     // @see http://www.thismuchiknow.co.uk/?p=64.
922     rangeIntersectsNode : function(range, node)
923     {
924         var nodeRange = node.ownerDocument.createRange();
925         try {
926             nodeRange.selectNode(node);
927         } catch (e) {
928             nodeRange.selectNodeContents(node);
929         }
930     
931         var rangeStartRange = range.cloneRange();
932         rangeStartRange.collapse(true);
933     
934         var rangeEndRange = range.cloneRange();
935         rangeEndRange.collapse(false);
936     
937         var nodeStartRange = nodeRange.cloneRange();
938         nodeStartRange.collapse(true);
939     
940         var nodeEndRange = nodeRange.cloneRange();
941         nodeEndRange.collapse(false);
942     
943         return rangeStartRange.compareBoundaryPoints(
944                  Range.START_TO_START, nodeEndRange) == -1 &&
945                rangeEndRange.compareBoundaryPoints(
946                  Range.START_TO_START, nodeStartRange) == 1;
947         
948          
949     },
950     rangeCompareNode : function(range, node)
951     {
952         var nodeRange = node.ownerDocument.createRange();
953         try {
954             nodeRange.selectNode(node);
955         } catch (e) {
956             nodeRange.selectNodeContents(node);
957         }
958         
959         
960         range.collapse(true);
961     
962         nodeRange.collapse(true);
963      
964         var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
965         var ee = range.compareBoundaryPoints(  Range.END_TO_END, nodeRange);
966          
967         //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
968         
969         var nodeIsBefore   =  ss == 1;
970         var nodeIsAfter    = ee == -1;
971         
972         if (nodeIsBefore && nodeIsAfter) {
973             return 0; // outer
974         }
975         if (!nodeIsBefore && nodeIsAfter) {
976             return 1; //right trailed.
977         }
978         
979         if (nodeIsBefore && !nodeIsAfter) {
980             return 2;  // left trailed.
981         }
982         // fully contined.
983         return 3;
984     },
985
986     // private? - in a new class?
987     cleanUpPaste :  function()
988     {
989         // cleans up the whole document..
990         Roo.log('cleanuppaste');
991         
992         this.cleanUpChildren(this.doc.body);
993         var clean = this.cleanWordChars(this.doc.body.innerHTML);
994         if (clean != this.doc.body.innerHTML) {
995             this.doc.body.innerHTML = clean;
996         }
997         
998     },
999     
1000     cleanWordChars : function(input) {// change the chars to hex code
1001         var he = Roo.HtmlEditorCore;
1002         
1003         var output = input;
1004         Roo.each(he.swapCodes, function(sw) { 
1005             var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1006             
1007             output = output.replace(swapper, sw[1]);
1008         });
1009         
1010         return output;
1011     },
1012     
1013     
1014     cleanUpChildren : function (n)
1015     {
1016         if (!n.childNodes.length) {
1017             return;
1018         }
1019         for (var i = n.childNodes.length-1; i > -1 ; i--) {
1020            this.cleanUpChild(n.childNodes[i]);
1021         }
1022     },
1023     
1024     
1025         
1026     
1027     cleanUpChild : function (node)
1028     {
1029         var ed = this;
1030         //console.log(node);
1031         if (node.nodeName == "#text") {
1032             // clean up silly Windows -- stuff?
1033             return; 
1034         }
1035         if (node.nodeName == "#comment" && !this.allowComments) {
1036             node.parentNode.removeChild(node);
1037             // clean up silly Windows -- stuff?
1038             return; 
1039         }
1040         var lcname = node.tagName.toLowerCase();
1041         // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full
1042         // whitelist of tags..
1043         
1044         if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
1045             // remove node.
1046             node.parentNode.removeChild(node);
1047             return;
1048             
1049         }
1050         
1051         var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1052         
1053         // spans with no attributes - just remove them..
1054         if ((!node.attributes || !node.attributes.length) && lcname == 'span') { 
1055             remove_keep_children = true;
1056         }
1057         
1058         // remove <a name=....> as rendering on yahoo mailer is borked with this.
1059         // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1060         
1061         //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1062         //    remove_keep_children = true;
1063         //}
1064         
1065         if (remove_keep_children) {
1066             this.cleanUpChildren(node);
1067             // inserts everything just before this node...
1068             while (node.childNodes.length) {
1069                 var cn = node.childNodes[0];
1070                 node.removeChild(cn);
1071                 node.parentNode.insertBefore(cn, node);
1072             }
1073             node.parentNode.removeChild(node);
1074             return;
1075         }
1076         
1077         if (!node.attributes || !node.attributes.length) {
1078             
1079           
1080             
1081             
1082             this.cleanUpChildren(node);
1083             return;
1084         }
1085         
1086         function cleanAttr(n,v)
1087         {
1088             
1089             if (v.match(/^\./) || v.match(/^\//)) {
1090                 return;
1091             }
1092             if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/) || v.match(/^ftp:/)) {
1093                 return;
1094             }
1095             if (v.match(/^#/)) {
1096                 return;
1097             }
1098             if (v.match(/^\{/)) { // allow template editing.
1099                 return;
1100             }
1101 //            Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1102             node.removeAttribute(n);
1103             
1104         }
1105         
1106         var cwhite = this.cwhite;
1107         var cblack = this.cblack;
1108             
1109         function cleanStyle(n,v)
1110         {
1111             if (v.match(/expression/)) { //XSS?? should we even bother..
1112                 node.removeAttribute(n);
1113                 return;
1114             }
1115             
1116             var parts = v.split(/;/);
1117             var clean = [];
1118             
1119             Roo.each(parts, function(p) {
1120                 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1121                 if (!p.length) {
1122                     return true;
1123                 }
1124                 var l = p.split(':').shift().replace(/\s+/g,'');
1125                 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1126                 
1127                 if ( cwhite.length && cblack.indexOf(l) > -1) {
1128 //                    Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1129                     //node.removeAttribute(n);
1130                     return true;
1131                 }
1132                 //Roo.log()
1133                 // only allow 'c whitelisted system attributes'
1134                 if ( cwhite.length &&  cwhite.indexOf(l) < 0) {
1135 //                    Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1136                     //node.removeAttribute(n);
1137                     return true;
1138                 }
1139                 
1140                 
1141                  
1142                 
1143                 clean.push(p);
1144                 return true;
1145             });
1146             if (clean.length) { 
1147                 node.setAttribute(n, clean.join(';'));
1148             } else {
1149                 node.removeAttribute(n);
1150             }
1151             
1152         }
1153         
1154         
1155         for (var i = node.attributes.length-1; i > -1 ; i--) {
1156             var a = node.attributes[i];
1157             //console.log(a);
1158             
1159             if (a.name.toLowerCase().substr(0,2)=='on')  {
1160                 node.removeAttribute(a.name);
1161                 continue;
1162             }
1163             if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1164                 node.removeAttribute(a.name);
1165                 continue;
1166             }
1167             if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1168                 cleanAttr(a.name,a.value); // fixme..
1169                 continue;
1170             }
1171             if (a.name == 'style') {
1172                 cleanStyle(a.name,a.value);
1173                 continue;
1174             }
1175             /// clean up MS crap..
1176             // tecnically this should be a list of valid class'es..
1177             
1178             
1179             if (a.name == 'class') {
1180                 if (a.value.match(/^Mso/)) {
1181                     node.removeAttribute('class');
1182                 }
1183                 
1184                 if (a.value.match(/^body$/)) {
1185                     node.removeAttribute('class');
1186                 }
1187                 continue;
1188             }
1189             
1190             // style cleanup!?
1191             // class cleanup?
1192             
1193         }
1194         
1195         
1196         this.cleanUpChildren(node);
1197         
1198         
1199     },
1200     
1201     /**
1202      * Clean up MS wordisms...
1203      */
1204     cleanWord : function(node)
1205     {
1206         if (!node) {
1207             this.cleanWord(this.doc.body);
1208             return;
1209         }
1210         
1211         if(
1212                 node.nodeName == 'SPAN' &&
1213                 !node.hasAttributes() &&
1214                 node.childNodes.length == 1 &&
1215                 node.firstChild.nodeName == "#text"  
1216         ) {
1217             var textNode = node.firstChild;
1218             node.removeChild(textNode);
1219             if (node.getAttribute('lang') != 'zh-CN') {   // do not space pad on chinese characters..
1220                 node.parentNode.insertBefore(node.ownerDocument.createTextNode(" "), node);
1221             }
1222             node.parentNode.insertBefore(textNode, node);
1223             if (node.getAttribute('lang') != 'zh-CN') {   // do not space pad on chinese characters..
1224                 node.parentNode.insertBefore(node.ownerDocument.createTextNode(" ") , node);
1225             }
1226             node.parentNode.removeChild(node);
1227         }
1228         
1229         if (node.nodeName == "#text") {
1230             // clean up silly Windows -- stuff?
1231             return; 
1232         }
1233         if (node.nodeName == "#comment") {
1234             node.parentNode.removeChild(node);
1235             // clean up silly Windows -- stuff?
1236             return; 
1237         }
1238         
1239         if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1240             node.parentNode.removeChild(node);
1241             return;
1242         }
1243         //Roo.log(node.tagName);
1244         // remove - but keep children..
1245         if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|v:|font)/)) {
1246             //Roo.log('-- removed');
1247             while (node.childNodes.length) {
1248                 var cn = node.childNodes[0];
1249                 node.removeChild(cn);
1250                 node.parentNode.insertBefore(cn, node);
1251                 // move node to parent - and clean it..
1252                 this.cleanWord(cn);
1253             }
1254             node.parentNode.removeChild(node);
1255             /// no need to iterate chidlren = it's got none..
1256             //this.iterateChildren(node, this.cleanWord);
1257             return;
1258         }
1259         // clean styles
1260         if (node.className.length) {
1261             
1262             var cn = node.className.split(/\W+/);
1263             var cna = [];
1264             Roo.each(cn, function(cls) {
1265                 if (cls.match(/Mso[a-zA-Z]+/)) {
1266                     return;
1267                 }
1268                 cna.push(cls);
1269             });
1270             node.className = cna.length ? cna.join(' ') : '';
1271             if (!cna.length) {
1272                 node.removeAttribute("class");
1273             }
1274         }
1275         
1276         if (node.hasAttribute("lang")) {
1277             node.removeAttribute("lang");
1278         }
1279         
1280         if (node.hasAttribute("style")) {
1281             
1282             var styles = node.getAttribute("style").split(";");
1283             var nstyle = [];
1284             Roo.each(styles, function(s) {
1285                 if (!s.match(/:/)) {
1286                     return;
1287                 }
1288                 var kv = s.split(":");
1289                 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1290                     return;
1291                 }
1292                 // what ever is left... we allow.
1293                 nstyle.push(s);
1294             });
1295             node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1296             if (!nstyle.length) {
1297                 node.removeAttribute('style');
1298             }
1299         }
1300         this.iterateChildren(node, this.cleanWord);
1301         
1302         
1303         
1304     },
1305     /**
1306      * iterateChildren of a Node, calling fn each time, using this as the scole..
1307      * @param {DomNode} node node to iterate children of.
1308      * @param {Function} fn method of this class to call on each item.
1309      */
1310     iterateChildren : function(node, fn)
1311     {
1312         if (!node.childNodes.length) {
1313                 return;
1314         }
1315         for (var i = node.childNodes.length-1; i > -1 ; i--) {
1316            fn.call(this, node.childNodes[i])
1317         }
1318     },
1319     
1320     
1321     /**
1322      * cleanTableWidths.
1323      *
1324      * Quite often pasting from word etc.. results in tables with column and widths.
1325      * This does not work well on fluid HTML layouts - like emails. - so this code should hunt an destroy them..
1326      *
1327      */
1328     cleanTableWidths : function(node)
1329     {
1330          
1331          
1332         if (!node) {
1333             this.cleanTableWidths(this.doc.body);
1334             return;
1335         }
1336         
1337         // ignore list...
1338         if (node.nodeName == "#text" || node.nodeName == "#comment") {
1339             return; 
1340         }
1341         Roo.log(node.tagName);
1342         if (!node.tagName.toLowerCase().match(/^(table|td|tr)$/)) {
1343             this.iterateChildren(node, this.cleanTableWidths);
1344             return;
1345         }
1346         if (node.hasAttribute('width')) {
1347             node.removeAttribute('width');
1348         }
1349         
1350          
1351         if (node.hasAttribute("style")) {
1352             // pretty basic...
1353             
1354             var styles = node.getAttribute("style").split(";");
1355             var nstyle = [];
1356             Roo.each(styles, function(s) {
1357                 if (!s.match(/:/)) {
1358                     return;
1359                 }
1360                 var kv = s.split(":");
1361                 if (kv[0].match(/^\s*(width|min-width)\s*$/)) {
1362                     return;
1363                 }
1364                 // what ever is left... we allow.
1365                 nstyle.push(s);
1366             });
1367             node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1368             if (!nstyle.length) {
1369                 node.removeAttribute('style');
1370             }
1371         }
1372         
1373         this.iterateChildren(node, this.cleanTableWidths);
1374         
1375         
1376     },
1377     
1378     
1379     
1380     
1381     domToHTML : function(currentElement, depth, nopadtext) {
1382         
1383         depth = depth || 0;
1384         nopadtext = nopadtext || false;
1385     
1386         if (!currentElement) {
1387             return this.domToHTML(this.doc.body);
1388         }
1389         
1390         //Roo.log(currentElement);
1391         var j;
1392         var allText = false;
1393         var nodeName = currentElement.nodeName;
1394         var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1395         
1396         if  (nodeName == '#text') {
1397             
1398             return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim();
1399         }
1400         
1401         
1402         var ret = '';
1403         if (nodeName != 'BODY') {
1404              
1405             var i = 0;
1406             // Prints the node tagName, such as <A>, <IMG>, etc
1407             if (tagName) {
1408                 var attr = [];
1409                 for(i = 0; i < currentElement.attributes.length;i++) {
1410                     // quoting?
1411                     var aname = currentElement.attributes.item(i).name;
1412                     if (!currentElement.attributes.item(i).value.length) {
1413                         continue;
1414                     }
1415                     attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1416                 }
1417                 
1418                 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1419             } 
1420             else {
1421                 
1422                 // eack
1423             }
1424         } else {
1425             tagName = false;
1426         }
1427         if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1428             return ret;
1429         }
1430         if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1431             nopadtext = true;
1432         }
1433         
1434         
1435         // Traverse the tree
1436         i = 0;
1437         var currentElementChild = currentElement.childNodes.item(i);
1438         var allText = true;
1439         var innerHTML  = '';
1440         lastnode = '';
1441         while (currentElementChild) {
1442             // Formatting code (indent the tree so it looks nice on the screen)
1443             var nopad = nopadtext;
1444             if (lastnode == 'SPAN') {
1445                 nopad  = true;
1446             }
1447             // text
1448             if  (currentElementChild.nodeName == '#text') {
1449                 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1450                 toadd = nopadtext ? toadd : toadd.trim();
1451                 if (!nopad && toadd.length > 80) {
1452                     innerHTML  += "\n" + (new Array( depth + 1 )).join( "  "  );
1453                 }
1454                 innerHTML  += toadd;
1455                 
1456                 i++;
1457                 currentElementChild = currentElement.childNodes.item(i);
1458                 lastNode = '';
1459                 continue;
1460             }
1461             allText = false;
1462             
1463             innerHTML  += nopad ? '' : "\n" + (new Array( depth + 1 )).join( "  "  );
1464                 
1465             // Recursively traverse the tree structure of the child node
1466             innerHTML   += this.domToHTML(currentElementChild, depth+1, nopadtext);
1467             lastnode = currentElementChild.nodeName;
1468             i++;
1469             currentElementChild=currentElement.childNodes.item(i);
1470         }
1471         
1472         ret += innerHTML;
1473         
1474         if (!allText) {
1475                 // The remaining code is mostly for formatting the tree
1476             ret+= nopadtext ? '' : "\n" + (new Array( depth  )).join( "  "  );
1477         }
1478         
1479         
1480         if (tagName) {
1481             ret+= "</"+tagName+">";
1482         }
1483         return ret;
1484         
1485     },
1486         
1487     applyBlacklists : function()
1488     {
1489         var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white  : [];
1490         var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black :  [];
1491         
1492         this.white = [];
1493         this.black = [];
1494         Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1495             if (b.indexOf(tag) > -1) {
1496                 return;
1497             }
1498             this.white.push(tag);
1499             
1500         }, this);
1501         
1502         Roo.each(w, function(tag) {
1503             if (b.indexOf(tag) > -1) {
1504                 return;
1505             }
1506             if (this.white.indexOf(tag) > -1) {
1507                 return;
1508             }
1509             this.white.push(tag);
1510             
1511         }, this);
1512         
1513         
1514         Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1515             if (w.indexOf(tag) > -1) {
1516                 return;
1517             }
1518             this.black.push(tag);
1519             
1520         }, this);
1521         
1522         Roo.each(b, function(tag) {
1523             if (w.indexOf(tag) > -1) {
1524                 return;
1525             }
1526             if (this.black.indexOf(tag) > -1) {
1527                 return;
1528             }
1529             this.black.push(tag);
1530             
1531         }, this);
1532         
1533         
1534         w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite  : [];
1535         b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack :  [];
1536         
1537         this.cwhite = [];
1538         this.cblack = [];
1539         Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1540             if (b.indexOf(tag) > -1) {
1541                 return;
1542             }
1543             this.cwhite.push(tag);
1544             
1545         }, this);
1546         
1547         Roo.each(w, function(tag) {
1548             if (b.indexOf(tag) > -1) {
1549                 return;
1550             }
1551             if (this.cwhite.indexOf(tag) > -1) {
1552                 return;
1553             }
1554             this.cwhite.push(tag);
1555             
1556         }, this);
1557         
1558         
1559         Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1560             if (w.indexOf(tag) > -1) {
1561                 return;
1562             }
1563             this.cblack.push(tag);
1564             
1565         }, this);
1566         
1567         Roo.each(b, function(tag) {
1568             if (w.indexOf(tag) > -1) {
1569                 return;
1570             }
1571             if (this.cblack.indexOf(tag) > -1) {
1572                 return;
1573             }
1574             this.cblack.push(tag);
1575             
1576         }, this);
1577     },
1578     
1579     setStylesheets : function(stylesheets)
1580     {
1581         if(typeof(stylesheets) == 'string'){
1582             Roo.get(this.iframe.contentDocument.head).createChild({
1583                 tag : 'link',
1584                 rel : 'stylesheet',
1585                 type : 'text/css',
1586                 href : stylesheets
1587             });
1588             
1589             return;
1590         }
1591         var _this = this;
1592      
1593         Roo.each(stylesheets, function(s) {
1594             if(!s.length){
1595                 return;
1596             }
1597             
1598             Roo.get(_this.iframe.contentDocument.head).createChild({
1599                 tag : 'link',
1600                 rel : 'stylesheet',
1601                 type : 'text/css',
1602                 href : s
1603             });
1604         });
1605
1606         
1607     },
1608     
1609     removeStylesheets : function()
1610     {
1611         var _this = this;
1612         
1613         Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1614             s.remove();
1615         });
1616     },
1617     
1618     setStyle : function(style)
1619     {
1620         Roo.get(this.iframe.contentDocument.head).createChild({
1621             tag : 'style',
1622             type : 'text/css',
1623             html : style
1624         });
1625
1626         return;
1627     }
1628     
1629     // hide stuff that is not compatible
1630     /**
1631      * @event blur
1632      * @hide
1633      */
1634     /**
1635      * @event change
1636      * @hide
1637      */
1638     /**
1639      * @event focus
1640      * @hide
1641      */
1642     /**
1643      * @event specialkey
1644      * @hide
1645      */
1646     /**
1647      * @cfg {String} fieldClass @hide
1648      */
1649     /**
1650      * @cfg {String} focusClass @hide
1651      */
1652     /**
1653      * @cfg {String} autoCreate @hide
1654      */
1655     /**
1656      * @cfg {String} inputType @hide
1657      */
1658     /**
1659      * @cfg {String} invalidClass @hide
1660      */
1661     /**
1662      * @cfg {String} invalidText @hide
1663      */
1664     /**
1665      * @cfg {String} msgFx @hide
1666      */
1667     /**
1668      * @cfg {String} validateOnBlur @hide
1669      */
1670 });
1671
1672 Roo.HtmlEditorCore.white = [
1673         'area', 'br', 'img', 'input', 'hr', 'wbr',
1674         
1675        'address', 'blockquote', 'center', 'dd',      'dir',       'div', 
1676        'dl',      'dt',         'h1',     'h2',      'h3',        'h4', 
1677        'h5',      'h6',         'hr',     'isindex', 'listing',   'marquee', 
1678        'menu',    'multicol',   'ol',     'p',       'plaintext', 'pre', 
1679        'table',   'ul',         'xmp', 
1680        
1681        'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', 
1682       'thead',   'tr', 
1683      
1684       'dir', 'menu', 'ol', 'ul', 'dl',
1685        
1686       'embed',  'object'
1687 ];
1688
1689
1690 Roo.HtmlEditorCore.black = [
1691     //    'embed',  'object', // enable - backend responsiblity to clean thiese
1692         'applet', // 
1693         'base',   'basefont', 'bgsound', 'blink',  'body', 
1694         'frame',  'frameset', 'head',    'html',   'ilayer', 
1695         'iframe', 'layer',  'link',     'meta',    'object',   
1696         'script', 'style' ,'title',  'xml' // clean later..
1697 ];
1698 Roo.HtmlEditorCore.clean = [
1699     'script', 'style', 'title', 'xml'
1700 ];
1701 Roo.HtmlEditorCore.remove = [
1702     'font'
1703 ];
1704 // attributes..
1705
1706 Roo.HtmlEditorCore.ablack = [
1707     'on'
1708 ];
1709     
1710 Roo.HtmlEditorCore.aclean = [ 
1711     'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc' 
1712 ];
1713
1714 // protocols..
1715 Roo.HtmlEditorCore.pwhite= [
1716         'http',  'https',  'mailto'
1717 ];
1718
1719 // white listed style attributes.
1720 Roo.HtmlEditorCore.cwhite= [
1721       //  'text-align', /// default is to allow most things..
1722       
1723          
1724 //        'font-size'//??
1725 ];
1726
1727 // black listed style attributes.
1728 Roo.HtmlEditorCore.cblack= [
1729       //  'font-size' -- this can be set by the project 
1730 ];
1731
1732
1733 Roo.HtmlEditorCore.swapCodes   =[ 
1734     [    8211, "&#8211;" ], 
1735     [    8212, "&#8212;" ], 
1736     [    8216,  "'" ],  
1737     [    8217, "'" ],  
1738     [    8220, '"' ],  
1739     [    8221, '"' ],  
1740     [    8226, "*" ],  
1741     [    8230, "..." ]
1742 ]; 
1743
1744