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