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