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