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         Roo.log('cleanUpChild!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
1001         var ed = this;
1002         //console.log(node);
1003         if (node.nodeName == "#text") {
1004             // clean up silly Windows -- stuff?
1005             return; 
1006         }
1007         if (node.nodeName == "#comment") {
1008             node.parentNode.removeChild(node);
1009             // clean up silly Windows -- stuff?
1010             return; 
1011         }
1012         var lcname = node.tagName.toLowerCase();
1013         // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full
1014         // whitelist of tags..
1015         
1016         if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
1017             // remove node.
1018             node.parentNode.removeChild(node);
1019             return;
1020             
1021         }
1022         
1023         var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1024         
1025         // remove <a name=....> as rendering on yahoo mailer is borked with this.
1026         // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1027         
1028         //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1029         //    remove_keep_children = true;
1030         //}
1031         
1032         if (remove_keep_children) {
1033             this.cleanUpChildren(node);
1034             // inserts everything just before this node...
1035             while (node.childNodes.length) {
1036                 var cn = node.childNodes[0];
1037                 node.removeChild(cn);
1038                 node.parentNode.insertBefore(cn, node);
1039             }
1040             node.parentNode.removeChild(node);
1041             return;
1042         }
1043         
1044         if (!node.attributes || !node.attributes.length) {
1045             this.cleanUpChildren(node);
1046             return;
1047         }
1048         
1049         function cleanAttr(n,v)
1050         {
1051             
1052             if (v.match(/^\./) || v.match(/^\//)) {
1053                 return;
1054             }
1055             if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1056                 return;
1057             }
1058             if (v.match(/^#/)) {
1059                 return;
1060             }
1061 //            Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1062             node.removeAttribute(n);
1063             
1064         }
1065         
1066         function cleanStyle(n,v)
1067         {
1068             if (v.match(/expression/)) { //XSS?? should we even bother..
1069                 node.removeAttribute(n);
1070                 return;
1071             }
1072             var cwhite = this.cwhite;
1073             var cblack = this.cblack;
1074             
1075             
1076             var parts = v.split(/;/);
1077             var clean = [];
1078             
1079             Roo.each(parts, function(p) {
1080                 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1081                 if (!p.length) {
1082                     return true;
1083                 }
1084                 var l = p.split(':').shift().replace(/\s+/g,'');
1085                 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1086                 
1087                 if ( cblack.length && cblack.indexOf(l) > -1) {
1088 //                    Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1089                     //node.removeAttribute(n);
1090                     return true;
1091                 }
1092                 //Roo.log()
1093                 // only allow 'c whitelisted system attributes'
1094                 if ( cwhite.length &&  cwhite.indexOf(l) < 0) {
1095 //                    Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1096                     //node.removeAttribute(n);
1097                     return true;
1098                 }
1099                 
1100                 
1101                  
1102                 
1103                 clean.push(p);
1104                 return true;
1105             });
1106             if (clean.length) { 
1107                 node.setAttribute(n, clean.join(';'));
1108             } else {
1109                 node.removeAttribute(n);
1110             }
1111             
1112         }
1113         
1114         
1115         for (var i = node.attributes.length-1; i > -1 ; i--) {
1116             var a = node.attributes[i];
1117             //console.log(a);
1118             
1119             if (a.name.toLowerCase().substr(0,2)=='on')  {
1120                 node.removeAttribute(a.name);
1121                 continue;
1122             }
1123             if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1124                 node.removeAttribute(a.name);
1125                 continue;
1126             }
1127             if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1128                 cleanAttr(a.name,a.value); // fixme..
1129                 continue;
1130             }
1131             if (a.name == 'style') {
1132                 cleanStyle(a.name,a.value);
1133                 continue;
1134             }
1135             /// clean up MS crap..
1136             // tecnically this should be a list of valid class'es..
1137             
1138             
1139             if (a.name == 'class') {
1140                 if (a.value.match(/^Mso/)) {
1141                     node.className = '';
1142                 }
1143                 
1144                 if (a.value.match(/body/)) {
1145                     node.className = '';
1146                 }
1147                 continue;
1148             }
1149             
1150             // style cleanup!?
1151             // class cleanup?
1152             
1153         }
1154         
1155         
1156         this.cleanUpChildren(node);
1157         
1158         
1159     },
1160     /**
1161      * Clean up MS wordisms...
1162      */
1163     cleanWord : function(node)
1164     {
1165         var _t = this;
1166         var cleanWordChildren = function()
1167         {
1168             if (!node.childNodes.length) {
1169                 return;
1170             }
1171             for (var i = node.childNodes.length-1; i > -1 ; i--) {
1172                _t.cleanWord(node.childNodes[i]);
1173             }
1174         }
1175         
1176         
1177         if (!node) {
1178             this.cleanWord(this.doc.body);
1179             return;
1180         }
1181         if (node.nodeName == "#text") {
1182             // clean up silly Windows -- stuff?
1183             return; 
1184         }
1185         if (node.nodeName == "#comment") {
1186             node.parentNode.removeChild(node);
1187             // clean up silly Windows -- stuff?
1188             return; 
1189         }
1190         
1191         if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1192             node.parentNode.removeChild(node);
1193             return;
1194         }
1195         
1196         // remove - but keep children..
1197         if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1198             while (node.childNodes.length) {
1199                 var cn = node.childNodes[0];
1200                 node.removeChild(cn);
1201                 node.parentNode.insertBefore(cn, node);
1202             }
1203             node.parentNode.removeChild(node);
1204             cleanWordChildren();
1205             return;
1206         }
1207         // clean styles
1208         if (node.className.length) {
1209             
1210             var cn = node.className.split(/\W+/);
1211             var cna = [];
1212             Roo.each(cn, function(cls) {
1213                 if (cls.match(/Mso[a-zA-Z]+/)) {
1214                     return;
1215                 }
1216                 cna.push(cls);
1217             });
1218             node.className = cna.length ? cna.join(' ') : '';
1219             if (!cna.length) {
1220                 node.removeAttribute("class");
1221             }
1222         }
1223         
1224         if (node.hasAttribute("lang")) {
1225             node.removeAttribute("lang");
1226         }
1227         
1228         if (node.hasAttribute("style")) {
1229             
1230             var styles = node.getAttribute("style").split(";");
1231             var nstyle = [];
1232             Roo.each(styles, function(s) {
1233                 if (!s.match(/:/)) {
1234                     return;
1235                 }
1236                 var kv = s.split(":");
1237                 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1238                     return;
1239                 }
1240                 // what ever is left... we allow.
1241                 nstyle.push(s);
1242             });
1243             node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1244             if (!nstyle.length) {
1245                 node.removeAttribute('style');
1246             }
1247         }
1248         
1249         cleanWordChildren();
1250         
1251         
1252     },
1253     domToHTML : function(currentElement, depth, nopadtext) {
1254         
1255         depth = depth || 0;
1256         nopadtext = nopadtext || false;
1257     
1258         if (!currentElement) {
1259             return this.domToHTML(this.doc.body);
1260         }
1261         
1262         //Roo.log(currentElement);
1263         var j;
1264         var allText = false;
1265         var nodeName = currentElement.nodeName;
1266         var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1267         
1268         if  (nodeName == '#text') {
1269             return currentElement.nodeValue;
1270         }
1271         
1272         
1273         var ret = '';
1274         if (nodeName != 'BODY') {
1275              
1276             var i = 0;
1277             // Prints the node tagName, such as <A>, <IMG>, etc
1278             if (tagName) {
1279                 var attr = [];
1280                 for(i = 0; i < currentElement.attributes.length;i++) {
1281                     // quoting?
1282                     var aname = currentElement.attributes.item(i).name;
1283                     if (!currentElement.attributes.item(i).value.length) {
1284                         continue;
1285                     }
1286                     attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1287                 }
1288                 
1289                 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1290             } 
1291             else {
1292                 
1293                 // eack
1294             }
1295         } else {
1296             tagName = false;
1297         }
1298         if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1299             return ret;
1300         }
1301         if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1302             nopadtext = true;
1303         }
1304         
1305         
1306         // Traverse the tree
1307         i = 0;
1308         var currentElementChild = currentElement.childNodes.item(i);
1309         var allText = true;
1310         var innerHTML  = '';
1311         lastnode = '';
1312         while (currentElementChild) {
1313             // Formatting code (indent the tree so it looks nice on the screen)
1314             var nopad = nopadtext;
1315             if (lastnode == 'SPAN') {
1316                 nopad  = true;
1317             }
1318             // text
1319             if  (currentElementChild.nodeName == '#text') {
1320                 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1321                 if (!nopad && toadd.length > 80) {
1322                     innerHTML  += "\n" + (new Array( depth + 1 )).join( "  "  );
1323                 }
1324                 innerHTML  += toadd;
1325                 
1326                 i++;
1327                 currentElementChild = currentElement.childNodes.item(i);
1328                 lastNode = '';
1329                 continue;
1330             }
1331             allText = false;
1332             
1333             innerHTML  += nopad ? '' : "\n" + (new Array( depth + 1 )).join( "  "  );
1334                 
1335             // Recursively traverse the tree structure of the child node
1336             innerHTML   += this.domToHTML(currentElementChild, depth+1, nopadtext);
1337             lastnode = currentElementChild.nodeName;
1338             i++;
1339             currentElementChild=currentElement.childNodes.item(i);
1340         }
1341         
1342         ret += innerHTML;
1343         
1344         if (!allText) {
1345                 // The remaining code is mostly for formatting the tree
1346             ret+= nopadtext ? '' : "\n" + (new Array( depth  )).join( "  "  );
1347         }
1348         
1349         
1350         if (tagName) {
1351             ret+= "</"+tagName+">";
1352         }
1353         return ret;
1354         
1355     },
1356         
1357     applyBlacklists : function()
1358     {
1359         Roo.log('applyBlacklists!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
1360         var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white  : [];
1361         var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black :  [];
1362         
1363         this.white = [];
1364         this.black = [];
1365         Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1366             if (b.indexOf(tag) > -1) {
1367                 return;
1368             }
1369             this.white.push(tag);
1370             
1371         }, this);
1372         
1373         Roo.each(w, function(tag) {
1374             if (b.indexOf(tag) > -1) {
1375                 return;
1376             }
1377             if (this.white.indexOf(tag) > -1) {
1378                 return;
1379             }
1380             this.white.push(tag);
1381             
1382         }, this);
1383         
1384         
1385         Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1386             if (w.indexOf(tag) > -1) {
1387                 return;
1388             }
1389             this.black.push(tag);
1390             
1391         }, this);
1392         
1393         Roo.each(b, function(tag) {
1394             if (w.indexOf(tag) > -1) {
1395                 return;
1396             }
1397             if (this.black.indexOf(tag) > -1) {
1398                 return;
1399             }
1400             this.black.push(tag);
1401             
1402         }, this);
1403         
1404         
1405         w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite  : [];
1406         b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack :  [];
1407         
1408         this.cwhite = [];
1409         this.cblack = [];
1410         Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1411             if (b.indexOf(tag) > -1) {
1412                 return;
1413             }
1414             this.cwhite.push(tag);
1415             
1416         }, this);
1417         
1418         Roo.each(w, function(tag) {
1419             if (b.indexOf(tag) > -1) {
1420                 return;
1421             }
1422             if (this.cwhite.indexOf(tag) > -1) {
1423                 return;
1424             }
1425             this.cwhite.push(tag);
1426             
1427         }, this);
1428         
1429         
1430         Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1431             if (w.indexOf(tag) > -1) {
1432                 return;
1433             }
1434             this.cblack.push(tag);
1435             
1436         }, this);
1437         
1438         Roo.each(b, function(tag) {
1439             if (w.indexOf(tag) > -1) {
1440                 return;
1441             }
1442             if (this.cblack.indexOf(tag) > -1) {
1443                 return;
1444             }
1445             this.cblack.push(tag);
1446             
1447         }, this);
1448     }
1449     
1450     // hide stuff that is not compatible
1451     /**
1452      * @event blur
1453      * @hide
1454      */
1455     /**
1456      * @event change
1457      * @hide
1458      */
1459     /**
1460      * @event focus
1461      * @hide
1462      */
1463     /**
1464      * @event specialkey
1465      * @hide
1466      */
1467     /**
1468      * @cfg {String} fieldClass @hide
1469      */
1470     /**
1471      * @cfg {String} focusClass @hide
1472      */
1473     /**
1474      * @cfg {String} autoCreate @hide
1475      */
1476     /**
1477      * @cfg {String} inputType @hide
1478      */
1479     /**
1480      * @cfg {String} invalidClass @hide
1481      */
1482     /**
1483      * @cfg {String} invalidText @hide
1484      */
1485     /**
1486      * @cfg {String} msgFx @hide
1487      */
1488     /**
1489      * @cfg {String} validateOnBlur @hide
1490      */
1491 });
1492
1493 Roo.HtmlEditorCore.white = [
1494         'area', 'br', 'img', 'input', 'hr', 'wbr',
1495         
1496        'address', 'blockquote', 'center', 'dd',      'dir',       'div', 
1497        'dl',      'dt',         'h1',     'h2',      'h3',        'h4', 
1498        'h5',      'h6',         'hr',     'isindex', 'listing',   'marquee', 
1499        'menu',    'multicol',   'ol',     'p',       'plaintext', 'pre', 
1500        'table',   'ul',         'xmp', 
1501        
1502        'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', 
1503       'thead',   'tr', 
1504      
1505       'dir', 'menu', 'ol', 'ul', 'dl',
1506        
1507       'embed',  'object'
1508 ];
1509
1510
1511 Roo.HtmlEditorCore.black = [
1512     //    'embed',  'object', // enable - backend responsiblity to clean thiese
1513         'applet', // 
1514         'base',   'basefont', 'bgsound', 'blink',  'body', 
1515         'frame',  'frameset', 'head',    'html',   'ilayer', 
1516         'iframe', 'layer',  'link',     'meta',    'object',   
1517         'script', 'style' ,'title',  'xml' // clean later..
1518 ];
1519 Roo.HtmlEditorCore.clean = [
1520     'script', 'style', 'title', 'xml'
1521 ];
1522 Roo.HtmlEditorCore.remove = [
1523     'font'
1524 ];
1525 // attributes..
1526
1527 Roo.HtmlEditorCore.ablack = [
1528     'on'
1529 ];
1530     
1531 Roo.HtmlEditorCore.aclean = [ 
1532     'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc' 
1533 ];
1534
1535 // protocols..
1536 Roo.HtmlEditorCore.pwhite= [
1537         'http',  'https',  'mailto'
1538 ];
1539
1540 // white listed style attributes.
1541 Roo.HtmlEditorCore.cwhite= [
1542       //  'text-align', /// default is to allow most things..
1543       
1544          
1545 //        'font-size'//??
1546 ];
1547
1548 // black listed style attributes.
1549 Roo.HtmlEditorCore.cblack= [
1550       //  'font-size' -- this can be set by the project 
1551 ];
1552
1553
1554 Roo.HtmlEditorCore.swapCodes   =[ 
1555     [    8211, "--" ], 
1556     [    8212, "--" ], 
1557     [    8216,  "'" ],  
1558     [    8217, "'" ],  
1559     [    8220, '"' ],  
1560     [    8221, '"' ],  
1561     [    8226, "*" ],  
1562     [    8230, "..." ]
1563 ]; 
1564
1565