c1785967d13f22331a91d40876bb7b3248b5852b
[roojs1] / Roo / form / HtmlEditor.js
1 //<script type="text/javascript">
2
3 /*
4  * Ext JS Library 1.1.1
5  * Copyright(c) 2006-2007, Ext JS, LLC.
6  * licensing@extjs.com
7  * 
8  * http://www.extjs.com/license
9  */
10  
11  /*
12   * 
13   * Known bugs:
14   * Default CSS appears to render it as fixed text by default (should really be Sans-Serif)
15   * - IE ? - no idea how much works there.
16   * 
17   * 
18   * 
19   */
20  
21
22 /**
23  * @class Ext.form.HtmlEditor
24  * @extends Ext.form.Field
25  * Provides a lightweight HTML Editor component.
26  * WARNING - THIS CURRENTlY ONLY WORKS ON FIREFOX - USE FCKeditor for a cross platform version
27  * 
28  * <br><br><b>Note: The focus/blur and validation marking functionality inherited from Ext.form.Field is NOT
29  * supported by this editor.</b><br/><br/>
30  * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within
31  * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
32  */
33 Roo.form.HtmlEditor = Roo.extend(Roo.form.Field, {
34       /**
35      * @cfg {Array} toolbars Array of toolbars. - defaults to just the Standard one
36      */
37     toolbars : false,
38     /**
39      * @cfg {String} createLinkText The default text for the create link prompt
40      */
41     createLinkText : 'Please enter the URL for the link:',
42     /**
43      * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
44      */
45     defaultLinkValue : 'http:/'+'/',
46    
47     
48     // id of frame..
49     frameId: false,
50     
51     // private properties
52     validationEvent : false,
53     deferHeight: true,
54     initialized : false,
55     activated : false,
56     sourceEditMode : false,
57     onFocus : Roo.emptyFn,
58     iframePad:3,
59     hideMode:'offsets',
60     defaultAutoCreate : {
61         tag: "textarea",
62         style:"width:500px;height:300px;",
63         autocomplete: "off"
64     },
65
66     // private
67     initComponent : function(){
68         this.addEvents({
69             /**
70              * @event initialize
71              * Fires when the editor is fully initialized (including the iframe)
72              * @param {HtmlEditor} this
73              */
74             initialize: true,
75             /**
76              * @event activate
77              * Fires when the editor is first receives the focus. Any insertion must wait
78              * until after this event.
79              * @param {HtmlEditor} this
80              */
81             activate: true,
82              /**
83              * @event beforesync
84              * Fires before the textarea is updated with content from the editor iframe. Return false
85              * to cancel the sync.
86              * @param {HtmlEditor} this
87              * @param {String} html
88              */
89             beforesync: true,
90              /**
91              * @event beforepush
92              * Fires before the iframe editor is updated with content from the textarea. Return false
93              * to cancel the push.
94              * @param {HtmlEditor} this
95              * @param {String} html
96              */
97             beforepush: true,
98              /**
99              * @event sync
100              * Fires when the textarea is updated with content from the editor iframe.
101              * @param {HtmlEditor} this
102              * @param {String} html
103              */
104             sync: true,
105              /**
106              * @event push
107              * Fires when the iframe editor is updated with content from the textarea.
108              * @param {HtmlEditor} this
109              * @param {String} html
110              */
111             push: true,
112              /**
113              * @event editmodechange
114              * Fires when the editor switches edit modes
115              * @param {HtmlEditor} this
116              * @param {Boolean} sourceEdit True if source edit, false if standard editing.
117              */
118             editmodechange: true,
119             /**
120              * @event editorevent
121              * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
122              * @param {HtmlEditor} this
123              */
124             editorevent: true
125         })
126     },
127
128     /**
129      * Protected method that will not generally be called directly. It
130      * is called when the editor creates its toolbar. Override this method if you need to
131      * add custom toolbar buttons.
132      * @param {HtmlEditor} editor
133      */
134     createToolbar : function(editor){
135         if (!editor.toolbars || !editor.toolbars.length) {
136             editor.toolbars = [ new Roo.form.HtmlEditor.ToolbarStandard() ]; // can be empty?
137         }
138         
139         for (var i =0 ; i < editor.toolbars.length;i++) {
140             editor.toolbars[i] = Roo.factory(editor.toolbars[i], Roo.form.HtmlEditor);
141             editor.toolbars[i].init(editor);
142         }
143          
144         
145     },
146
147     /**
148      * Protected method that will not generally be called directly. It
149      * is called when the editor initializes the iframe with HTML contents. Override this method if you
150      * want to change the initialization markup of the iframe (e.g. to add stylesheets).
151      */
152     getDocMarkup : function(){
153         return '<html><head><style type="text/css">body{border:0;margin:0;padding:3px;height:98%;cursor:text;}</style></head><body></body></html>';
154     },
155
156     // private
157     onRender : function(ct, position){
158         Roo.form.HtmlEditor.superclass.onRender.call(this, ct, position);
159         this.el.dom.style.border = '0 none';
160         this.el.dom.setAttribute('tabIndex', -1);
161         this.el.addClass('x-hidden');
162         if(Roo.isIE){ // fix IE 1px bogus margin
163             this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
164         }
165         this.wrap = this.el.wrap({
166             cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
167         });
168
169         this.frameId = Roo.id();
170         this.createToolbar(this);
171         
172         
173         
174         
175       
176         
177         var iframe = this.wrap.createChild({
178             tag: 'iframe',
179             id: this.frameId,
180             name: this.frameId,
181             frameBorder : 'no',
182             'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL  :  "javascript:false"
183         });
184         
185        // console.log(iframe);
186         //this.wrap.dom.appendChild(iframe);
187
188         this.iframe = iframe.dom;
189
190          this.assignDocWin();
191         
192         this.doc.designMode = 'on';
193        
194         this.doc.open();
195         this.doc.write(this.getDocMarkup());
196         this.doc.close();
197
198         
199         var task = { // must defer to wait for browser to be ready
200             run : function(){
201                 //console.log("run task?" + this.doc.readyState);
202                 this.assignDocWin();
203                 if(this.doc.body || this.doc.readyState == 'complete'){
204                     try {
205                         this.doc.designMode="on";
206                     } catch (e) {
207                         return;
208                     }
209                     Roo.TaskMgr.stop(task);
210                     this.initEditor.defer(10, this);
211                 }
212             },
213             interval : 10,
214             duration:10000,
215             scope: this
216         };
217         Roo.TaskMgr.start(task);
218
219         if(!this.width){
220             this.setSize(this.el.getSize());
221         }
222     },
223
224     // private
225     onResize : function(w, h){
226         Roo.form.HtmlEditor.superclass.onResize.apply(this, arguments);
227         if(this.el && this.iframe){
228             if(typeof w == 'number'){
229                 var aw = w - this.wrap.getFrameWidth('lr');
230                 this.el.setWidth(this.adjustWidth('textarea', aw));
231                 this.iframe.style.width = aw + 'px';
232             }
233             if(typeof h == 'number'){
234                 var tbh = 0;
235                 for (var i =0; i < this.toolbars.length;i++) {
236                     // fixme - ask toolbars for heights?
237                     tbh += this.toolbars[i].tb.el.getHeight();
238                 }
239                 
240                 
241                 
242                 
243                 var ah = h - this.wrap.getFrameWidth('tb') - tbh;// this.tb.el.getHeight();
244                 this.el.setHeight(this.adjustWidth('textarea', ah));
245                 this.iframe.style.height = ah + 'px';
246                 if(this.doc){
247                     (this.doc.body || this.doc.documentElement).style.height = (ah - (this.iframePad*2)) + 'px';
248                 }
249             }
250         }
251     },
252
253     /**
254      * Toggles the editor between standard and source edit mode.
255      * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
256      */
257     toggleSourceEdit : function(sourceEditMode){
258         
259         this.sourceEditMode = sourceEditMode === true;
260         
261         if(this.sourceEditMode){
262           
263             this.syncValue();
264             this.iframe.className = 'x-hidden';
265             this.el.removeClass('x-hidden');
266             this.el.dom.removeAttribute('tabIndex');
267             this.el.focus();
268         }else{
269              
270             this.pushValue();
271             this.iframe.className = '';
272             this.el.addClass('x-hidden');
273             this.el.dom.setAttribute('tabIndex', -1);
274             this.deferFocus();
275         }
276         this.setSize(this.wrap.getSize());
277         this.fireEvent('editmodechange', this, this.sourceEditMode);
278     },
279
280     // private used internally
281     createLink : function(){
282         var url = prompt(this.createLinkText, this.defaultLinkValue);
283         if(url && url != 'http:/'+'/'){
284             this.relayCmd('createlink', url);
285         }
286     },
287
288     // private (for BoxComponent)
289     adjustSize : Roo.BoxComponent.prototype.adjustSize,
290
291     // private (for BoxComponent)
292     getResizeEl : function(){
293         return this.wrap;
294     },
295
296     // private (for BoxComponent)
297     getPositionEl : function(){
298         return this.wrap;
299     },
300
301     // private
302     initEvents : function(){
303         this.originalValue = this.getValue();
304     },
305
306     /**
307      * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
308      * @method
309      */
310     markInvalid : Roo.emptyFn,
311     /**
312      * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
313      * @method
314      */
315     clearInvalid : Roo.emptyFn,
316
317     setValue : function(v){
318         Roo.form.HtmlEditor.superclass.setValue.call(this, v);
319         this.pushValue();
320     },
321
322     /**
323      * Protected method that will not generally be called directly. If you need/want
324      * custom HTML cleanup, this is the method you should override.
325      * @param {String} html The HTML to be cleaned
326      * return {String} The cleaned HTML
327      */
328     cleanHtml : function(html){
329         html = String(html);
330         if(html.length > 5){
331             if(Roo.isSafari){ // strip safari nonsense
332                 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
333             }
334         }
335         if(html == '&nbsp;'){
336             html = '';
337         }
338         return html;
339     },
340
341     /**
342      * Protected method that will not generally be called directly. Syncs the contents
343      * of the editor iframe with the textarea.
344      */
345     syncValue : function(){
346         if(this.initialized){
347             var bd = (this.doc.body || this.doc.documentElement);
348             var html = bd.innerHTML;
349             if(Roo.isSafari){
350                 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
351                 var m = bs.match(/text-align:(.*?);/i);
352                 if(m && m[1]){
353                     html = '<div style="'+m[0]+'">' + html + '</div>';
354                 }
355             }
356             html = this.cleanHtml(html);
357             if(this.fireEvent('beforesync', this, html) !== false){
358                 this.el.dom.value = html;
359                 this.fireEvent('sync', this, html);
360             }
361         }
362     },
363
364     /**
365      * Protected method that will not generally be called directly. Pushes the value of the textarea
366      * into the iframe editor.
367      */
368     pushValue : function(){
369         if(this.initialized){
370             var v = this.el.dom.value;
371             if(v.length < 1){
372                 v = '&#160;';
373             }
374             if(this.fireEvent('beforepush', this, v) !== false){
375                 (this.doc.body || this.doc.documentElement).innerHTML = v;
376                 this.fireEvent('push', this, v);
377             }
378         }
379     },
380
381     // private
382     deferFocus : function(){
383         this.focus.defer(10, this);
384     },
385
386     // doc'ed in Field
387     focus : function(){
388         if(this.win && !this.sourceEditMode){
389             this.win.focus();
390         }else{
391             this.el.focus();
392         }
393     },
394     
395     assignDocWin: function()
396     {
397         var iframe = this.iframe;
398         
399          if(Roo.isIE){
400             this.doc = iframe.contentWindow.document;
401             this.win = iframe.contentWindow;
402         } else {
403             if (!Roo.get(this.frameId)) {
404                 return;
405             }
406             this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
407             this.win = Roo.get(this.frameId).dom.contentWindow;
408         }
409     },
410     
411     // private
412     initEditor : function(){
413         //console.log("INIT EDITOR");
414         this.assignDocWin();
415         
416         
417         
418         this.doc.designMode="on";
419         this.doc.open();
420         this.doc.write(this.getDocMarkup());
421         this.doc.close();
422         
423         var dbody = (this.doc.body || this.doc.documentElement);
424         //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
425         // this copies styles from the containing element into thsi one..
426         // not sure why we need all of this..
427         var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
428         ss['background-attachment'] = 'fixed'; // w3c
429         dbody.bgProperties = 'fixed'; // ie
430         Roo.DomHelper.applyStyles(dbody, ss);
431         Roo.EventManager.on(this.doc, {
432             'mousedown': this.onEditorEvent,
433             'dblclick': this.onEditorEvent,
434             'click': this.onEditorEvent,
435             'keyup': this.onEditorEvent,
436             buffer:100,
437             scope: this
438         });
439         if(Roo.isGecko){
440             Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
441         }
442         if(Roo.isIE || Roo.isSafari || Roo.isOpera){
443             Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
444         }
445         this.initialized = true;
446
447         this.fireEvent('initialize', this);
448         this.pushValue();
449     },
450
451     // private
452     onDestroy : function(){
453         
454         
455         
456         if(this.rendered){
457             
458             for (var i =0; i < this.toolbars.length;i++) {
459                 // fixme - ask toolbars for heights?
460                 this.toolbars[i].onDestroy();
461             }
462             
463             this.wrap.dom.innerHTML = '';
464             this.wrap.remove();
465         }
466     },
467
468     // private
469     onFirstFocus : function(){
470         
471         this.assignDocWin();
472         
473         
474         this.activated = true;
475         for (var i =0; i < this.toolbars.length;i++) {
476             this.toolbars[i].onFirstFocus();
477         }
478        
479         if(Roo.isGecko){ // prevent silly gecko errors
480             this.win.focus();
481             var s = this.win.getSelection();
482             if(!s.focusNode || s.focusNode.nodeType != 3){
483                 var r = s.getRangeAt(0);
484                 r.selectNodeContents((this.doc.body || this.doc.documentElement));
485                 r.collapse(true);
486                 this.deferFocus();
487             }
488             try{
489                 this.execCmd('useCSS', true);
490                 this.execCmd('styleWithCSS', false);
491             }catch(e){}
492         }
493         this.fireEvent('activate', this);
494     },
495
496     // private
497     adjustFont: function(btn){
498         var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
499         //if(Roo.isSafari){ // safari
500         //    adjust *= 2;
501        // }
502         var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
503         if(Roo.isSafari){ // safari
504             var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
505             v =  (v < 10) ? 10 : v;
506             v =  (v > 48) ? 48 : v;
507             v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
508             
509         }
510         
511         
512         v = Math.max(1, v+adjust);
513         
514         this.execCmd('FontSize', v  );
515     },
516
517     onEditorEvent : function(e){
518         this.fireEvent('editorevent', this, e);
519       //  this.updateToolbar();
520         this.syncValue();
521     },
522
523     insertTag : function(tg)
524     {
525         // could be a bit smarter... -> wrap the current selected tRoo..
526         
527         this.execCmd("formatblock",   tg);
528         
529     },
530     
531     insertText : function(txt)
532     {
533         
534         
535         range = this.createRange();
536         range.deleteContents();
537                //alert(Sender.getAttribute('label'));
538                
539         range.insertNode(this.doc.createTextNode(txt));
540     } ,
541     
542     // private
543     relayBtnCmd : function(btn){
544         this.relayCmd(btn.cmd);
545     },
546
547     /**
548      * Executes a Midas editor command on the editor document and performs necessary focus and
549      * toolbar updates. <b>This should only be called after the editor is initialized.</b>
550      * @param {String} cmd The Midas command
551      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
552      */
553     relayCmd : function(cmd, value){
554         this.win.focus();
555         this.execCmd(cmd, value);
556         this.fireEvent('editorevent', this);
557         //this.updateToolbar();
558         this.deferFocus();
559     },
560
561     /**
562      * Executes a Midas editor command directly on the editor document.
563      * For visual commands, you should use {@link #relayCmd} instead.
564      * <b>This should only be called after the editor is initialized.</b>
565      * @param {String} cmd The Midas command
566      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
567      */
568     execCmd : function(cmd, value){
569         this.doc.execCommand(cmd, false, value === undefined ? null : value);
570         this.syncValue();
571     },
572
573    
574     /**
575      * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
576      * to insert tRoo.
577      * @param {String} text
578      */
579     insertAtCursor : function(text){
580         if(!this.activated){
581             return;
582         }
583         if(Roo.isIE){
584             this.win.focus();
585             var r = this.doc.selection.createRange();
586             if(r){
587                 r.collapse(true);
588                 r.pasteHTML(text);
589                 this.syncValue();
590                 this.deferFocus();
591             }
592         }else if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
593             this.win.focus();
594             this.execCmd('InsertHTML', text);
595             this.deferFocus();
596         }
597     },
598  // private
599     mozKeyPress : function(e){
600         if(e.ctrlKey){
601             var c = e.getCharCode(), cmd;
602           
603             if(c > 0){
604                 c = String.fromCharCode(c).toLowerCase();
605                 switch(c){
606                     case 'b':
607                         cmd = 'bold';
608                     break;
609                     case 'i':
610                         cmd = 'italic';
611                     break;
612                     case 'u':
613                         cmd = 'underline';
614                     case 'v':
615                         this.cleanUpPaste.defer(100, this);
616                         return;
617                     break;
618                 }
619                 if(cmd){
620                     this.win.focus();
621                     this.execCmd(cmd);
622                     this.deferFocus();
623                     e.preventDefault();
624                 }
625                 
626             }
627         }
628     },
629
630     // private
631     fixKeys : function(){ // load time branching for fastest keydown performance
632         if(Roo.isIE){
633             return function(e){
634                 var k = e.getKey(), r;
635                 if(k == e.TAB){
636                     e.stopEvent();
637                     r = this.doc.selection.createRange();
638                     if(r){
639                         r.collapse(true);
640                         r.pasteHTML('&#160;&#160;&#160;&#160;');
641                         this.deferFocus();
642                     }
643                     return;
644                 }
645                 
646                 if(k == e.ENTER){
647                     r = this.doc.selection.createRange();
648                     if(r){
649                         var target = r.parentElement();
650                         if(!target || target.tagName.toLowerCase() != 'li'){
651                             e.stopEvent();
652                             r.pasteHTML('<br />');
653                             r.collapse(false);
654                             r.select();
655                         }
656                     }
657                 }
658                 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
659                     this.cleanUpPaste.defer(100, this);
660                     return;
661                 }
662                 
663                 
664             };
665         }else if(Roo.isOpera){
666             return function(e){
667                 var k = e.getKey();
668                 if(k == e.TAB){
669                     e.stopEvent();
670                     this.win.focus();
671                     this.execCmd('InsertHTML','&#160;&#160;&#160;&#160;');
672                     this.deferFocus();
673                 }
674                 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
675                     this.cleanUpPaste.defer(100, this);
676                     return;
677                 }
678                 
679             };
680         }else if(Roo.isSafari){
681             return function(e){
682                 var k = e.getKey();
683                 
684                 if(k == e.TAB){
685                     e.stopEvent();
686                     this.execCmd('InsertText','\t');
687                     this.deferFocus();
688                     return;
689                 }
690                if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
691                     this.cleanUpPaste.defer(100, this);
692                     return;
693                 }
694                 
695              };
696         }
697     }(),
698     
699     getAllAncestors: function()
700     {
701         var p = this.getSelectedNode();
702         var a = [];
703         if (!p) {
704             a.push(p); // push blank onto stack..
705             p = this.getParentElement();
706         }
707         
708         
709         while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
710             a.push(p);
711             p = p.parentNode;
712         }
713         a.push(this.doc.body);
714         return a;
715     },
716     lastSel : false,
717     lastSelNode : false,
718     
719     
720     getSelection : function() 
721     {
722         this.assignDocWin();
723         return Roo.isIE ? this.doc.selection : this.win.getSelection();
724     },
725     
726     getSelectedNode: function() 
727     {
728         // this may only work on Gecko!!!
729         
730         // should we cache this!!!!
731         
732         
733         
734          
735         var range = this.createRange(this.getSelection());
736         
737         if (Roo.isIE) {
738             var parent = range.parentElement();
739             while (true) {
740                 var testRange = range.duplicate();
741                 testRange.moveToElementText(parent);
742                 if (testRange.inRange(range)) {
743                     break;
744                 }
745                 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
746                     break;
747                 }
748                 parent = parent.parentElement;
749             }
750             return parent;
751         }
752         
753         
754         var ar = range.endContainer.childNodes;
755         if (!ar.length) {
756             ar = range.commonAncestorContainer.childNodes;
757             //alert(ar.length);
758         }
759         var nodes = [];
760         var other_nodes = [];
761         var has_other_nodes = false;
762         for (var i=0;i<ar.length;i++) {
763             if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ? 
764                 continue;
765             }
766             // fullly contained node.
767             
768             if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
769                 nodes.push(ar[i]);
770                 continue;
771             }
772             
773             // probably selected..
774             if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
775                 other_nodes.push(ar[i]);
776                 continue;
777             }
778             if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0))  {
779                 continue;
780             }
781             
782             
783             has_other_nodes = true;
784         }
785         if (!nodes.length && other_nodes.length) {
786             nodes= other_nodes;
787         }
788         if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
789             return false;
790         }
791         
792         return nodes[0];
793     },
794     createRange: function(sel)
795     {
796         // this has strange effects when using with 
797         // top toolbar - not sure if it's a great idea.
798         //this.editor.contentWindow.focus();
799         if (typeof sel != "undefined") {
800             try {
801                 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
802             } catch(e) {
803                 return this.doc.createRange();
804             }
805         } else {
806             return this.doc.createRange();
807         }
808     },
809     getParentElement: function()
810     {
811         
812         this.assignDocWin();
813         var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
814         
815         var range = this.createRange(sel);
816          
817         try {
818             var p = range.commonAncestorContainer;
819             while (p.nodeType == 3) { // text node
820                 p = p.parentNode;
821             }
822             return p;
823         } catch (e) {
824             return null;
825         }
826     
827     },
828     
829     
830     
831     // BC Hacks - cause I cant work out what i was trying to do..
832     rangeIntersectsNode : function(range, node)
833     {
834         var nodeRange = node.ownerDocument.createRange();
835         try {
836             nodeRange.selectNode(node);
837         }
838         catch (e) {
839             nodeRange.selectNodeContents(node);
840         }
841
842         return range.compareBoundaryPoints(Range.END_TO_START, nodeRange) == -1 &&
843                  range.compareBoundaryPoints(Range.START_TO_END, nodeRange) == 1;
844     },
845     rangeCompareNode : function(range, node) {
846         var nodeRange = node.ownerDocument.createRange();
847         try {
848             nodeRange.selectNode(node);
849         } catch (e) {
850             nodeRange.selectNodeContents(node);
851         }
852         var nodeIsBefore = range.compareBoundaryPoints(Range.START_TO_START, nodeRange) == 1;
853         var nodeIsAfter = range.compareBoundaryPoints(Range.END_TO_END, nodeRange) == -1;
854
855         if (nodeIsBefore && !nodeIsAfter)
856             return 0;
857         if (!nodeIsBefore && nodeIsAfter)
858             return 1;
859         if (nodeIsBefore && nodeIsAfter)
860             return 2;
861
862         return 3;
863     },
864
865     // private? - in a new class?
866     cleanUpPaste :  function()
867     {
868         // cleans up the whole document..
869       //  console.log('cleanuppaste');
870         this.cleanUpChildren(this.doc.body)
871         
872         
873     },
874     cleanUpChildren : function (n)
875     {
876         if (!n.childNodes.length) {
877             return;
878         }
879         for (var i = n.childNodes.length-1; i > -1 ; i--) {
880            this.cleanUpChild(n.childNodes[i]);
881         }
882     },
883     
884     
885         
886     
887     cleanUpChild : function (node)
888     {
889         //console.log(node);
890         if (node.nodeName == "#text") {
891             // clean up silly Windows -- stuff?
892             return; 
893         }
894         if (node.nodeName == "#comment") {
895             node.parentNode.removeChild(node);
896             // clean up silly Windows -- stuff?
897             return; 
898         }
899         
900         if (Roo.form.HtmlEditor.black.indexOf(node.tagName.toLowerCase()) > -1) {
901             // remove node.
902             node.parentNode.removeChild(node);
903             return;
904             
905         }
906         if (!node.attributes || !node.attributes.length) {
907             this.cleanUpChildren(node);
908             return;
909         }
910         
911         function cleanAttr(n,v)
912         {
913             
914             if (v.match(/^\./) || v.match(/^\//)) {
915                 return;
916             }
917             if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
918                 return;
919             }
920             Roo.log("(REMOVE)"+ node.tagName +'.' + n + '=' + v);
921             node.removeAttribute(n);
922             
923         }
924         
925         function cleanStyle(n,v)
926         {
927             if (v.match(/expression/)) { //XSS?? should we even bother..
928                 node.removeAttribute(n);
929                 return;
930             }
931             
932             
933             var parts = v.split(/;/);
934             Roo.each(parts, function(p) {
935                 p = p.replace(/\s+/g,'');
936                 if (!p.length) {
937                     return;
938                 }
939                 var l = p.split(':').shift().replace(/\s+/g,'');
940                 
941                 if (Roo.form.HtmlEditor.cwhite.indexOf(l) < 0) {
942                     Roo.log('(REMOVE)' + node.tagName +'.' + n + ':'+l + '=' + v);
943                     node.removeAttribute(n);
944                     return false;
945                 }
946             });
947             
948             
949         }
950         
951         
952         for (var i = node.attributes.length-1; i > -1 ; i--) {
953             var a = node.attributes[i];
954             //console.log(a);
955             if (Roo.form.HtmlEditor.ablack.indexOf(a.name.toLowerCase()) > -1) {
956                 node.removeAttribute(a.name);
957                 return;
958             }
959             if (Roo.form.HtmlEditor.aclean.indexOf(a.name.toLowerCase()) > -1) {
960                 cleanAttr(a.name,a.value); // fixme..
961                 return;
962             }
963             if (a.name == 'style') {
964                 cleanStyle(a.name,a.value);
965             }
966             /// clean up MS crap..
967             if (a.name == 'class') {
968                 if (a.value.match(/^Mso/)) {
969                     node.className = '';
970                 }
971             }
972             
973             // style cleanup!?
974             // class cleanup?
975             
976         }
977         
978         
979         this.cleanUpChildren(node);
980         
981         
982     }
983     
984     
985     // hide stuff that is not compatible
986     /**
987      * @event blur
988      * @hide
989      */
990     /**
991      * @event change
992      * @hide
993      */
994     /**
995      * @event focus
996      * @hide
997      */
998     /**
999      * @event specialkey
1000      * @hide
1001      */
1002     /**
1003      * @cfg {String} fieldClass @hide
1004      */
1005     /**
1006      * @cfg {String} focusClass @hide
1007      */
1008     /**
1009      * @cfg {String} autoCreate @hide
1010      */
1011     /**
1012      * @cfg {String} inputType @hide
1013      */
1014     /**
1015      * @cfg {String} invalidClass @hide
1016      */
1017     /**
1018      * @cfg {String} invalidText @hide
1019      */
1020     /**
1021      * @cfg {String} msgFx @hide
1022      */
1023     /**
1024      * @cfg {String} validateOnBlur @hide
1025      */
1026 });
1027
1028 Roo.form.HtmlEditor.white = [
1029         'area', 'br', 'img', 'input', 'hr', 'wbr',
1030         
1031        'address', 'blockquote', 'center', 'dd',      'dir',       'div', 
1032        'dl',      'dt',         'h1',     'h2',      'h3',        'h4', 
1033        'h5',      'h6',         'hr',     'isindex', 'listing',   'marquee', 
1034        'menu',    'multicol',   'ol',     'p',       'plaintext', 'pre', 
1035        'table',   'ul',         'xmp', 
1036        
1037        'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', 
1038       'thead',   'tr', 
1039      
1040       'dir', 'menu', 'ol', 'ul', 'dl',
1041        
1042       'embed',  'object'
1043 ];
1044
1045
1046 Roo.form.HtmlEditor.black = [
1047     //    'embed',  'object', // enable - backend responsiblity to clean thiese
1048         'applet', // 
1049         'base',   'basefont', 'bgsound', 'blink',  'body', 
1050         'frame',  'frameset', 'head',    'html',   'ilayer', 
1051         'iframe', 'layer',  'link',     'meta',    'object',   
1052         'script', 'style' ,'title',  'xml' // clean later..
1053 ];
1054 Roo.form.HtmlEditor.clean = [
1055     'script', 'style', 'title', 'xml'
1056 ];
1057
1058 // attributes..
1059
1060 Roo.form.HtmlEditor.ablack = [
1061     'on'
1062 ];
1063     
1064 Roo.form.HtmlEditor.aclean = [ 
1065     'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1066 ];
1067
1068 // protocols..
1069 Roo.form.HtmlEditor.pwhite= [
1070         'http',  'https',  'mailto'
1071 ];
1072
1073 Roo.form.HtmlEditor.cwhite= [
1074         'text-align',
1075         'font-size'
1076 ];
1077