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