disable tidy serializer when autoclean is disabled.
[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     
79     // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
80     
81     // defaults : white / black...
82     this.applyBlacklists();
83     
84     
85     
86 };
87
88
89 Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
90
91
92      /**
93      * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field 
94      */
95     
96     owner : false,
97     
98      /**
99      * @cfg {String} resizable  's' or 'se' or 'e' - wrapps the element in a
100      *                        Roo.resizable.
101      */
102     resizable : false,
103      /**
104      * @cfg {Number} height (in pixels)
105      */   
106     height: 300,
107    /**
108      * @cfg {Number} width (in pixels)
109      */   
110     width: 500,
111      /**
112      * @cfg {boolean} autoClean - default true - loading and saving will remove quite a bit of formating,
113      *         if you are doing an email editor, this probably needs disabling, it's designed
114      */
115     autoClean: true,
116     
117     /**
118      * @cfg {boolean} enableBlocks - default true - if the block editor (table and figure should be enabled)
119      */
120     enableBlocks : true,
121     /**
122      * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
123      * 
124      */
125     stylesheets: false,
126      /**
127      * @cfg {String} language default en - language of text (usefull for rtl languages)
128      * 
129      */
130     language: 'en',
131     
132     /**
133      * @cfg {boolean} allowComments - default false - allow comments in HTML source
134      *          - by default they are stripped - if you are editing email you may need this.
135      */
136     allowComments: false,
137     // id of frame..
138     frameId: false,
139     
140     // private properties
141     validationEvent : false,
142     deferHeight: true,
143     initialized : false,
144     activated : false,
145     sourceEditMode : false,
146     onFocus : Roo.emptyFn,
147     iframePad:3,
148     hideMode:'offsets',
149     
150     clearUp: true,
151     
152     // blacklist + whitelisted elements..
153     black: false,
154     white: false,
155      
156     bodyCls : '',
157
158     
159     undoManager : false,
160     /**
161      * Protected method that will not generally be called directly. It
162      * is called when the editor initializes the iframe with HTML contents. Override this method if you
163      * want to change the initialization markup of the iframe (e.g. to add stylesheets).
164      */
165     getDocMarkup : function(){
166         // body styles..
167         var st = '';
168         
169         // inherit styels from page...?? 
170         if (this.stylesheets === false) {
171             
172             Roo.get(document.head).select('style').each(function(node) {
173                 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
174             });
175             
176             Roo.get(document.head).select('link').each(function(node) { 
177                 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
178             });
179             
180         } else if (!this.stylesheets.length) {
181                 // simple..
182                 st = '<style type="text/css">' +
183                     'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
184                    '</style>';
185         } else {
186             for (var i in this.stylesheets) {
187                 if (typeof(this.stylesheets[i]) != 'string') {
188                     continue;
189                 }
190                 st += '<link rel="stylesheet" href="' + this.stylesheets[i] +'" type="text/css">';
191             }
192             
193         }
194         
195         st +=  '<style type="text/css">' +
196             'IMG { cursor: pointer } ' +
197         '</style>';
198         
199         st += '<meta name="google" content="notranslate">';
200         
201         var cls = 'notranslate roo-htmleditor-body';
202         
203         if(this.bodyCls.length){
204             cls += ' ' + this.bodyCls;
205         }
206         
207         return '<html  class="notranslate" translate="no"><head>' + st  +
208             //<style type="text/css">' +
209             //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
210             //'</style>' +
211             ' </head><body contenteditable="true" data-enable-grammerly="true" class="' +  cls + '"></body></html>';
212     },
213
214     // private
215     onRender : function(ct, position)
216     {
217         var _t = this;
218         //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
219         this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
220         
221         
222         this.el.dom.style.border = '0 none';
223         this.el.dom.setAttribute('tabIndex', -1);
224         this.el.addClass('x-hidden hide');
225         
226         
227         
228         if(Roo.isIE){ // fix IE 1px bogus margin
229             this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
230         }
231        
232         
233         this.frameId = Roo.id();
234         
235          
236         
237         var iframe = this.owner.wrap.createChild({
238             tag: 'iframe',
239             cls: 'form-control', // bootstrap..
240             id: this.frameId,
241             name: this.frameId,
242             frameBorder : 'no',
243             'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL  :  "javascript:false"
244         }, this.el
245         );
246         
247         
248         this.iframe = iframe.dom;
249
250         this.assignDocWin();
251         
252         this.doc.designMode = 'on';
253        
254         this.doc.open();
255         this.doc.write(this.getDocMarkup());
256         this.doc.close();
257
258         
259         var task = { // must defer to wait for browser to be ready
260             run : function(){
261                 //console.log("run task?" + this.doc.readyState);
262                 this.assignDocWin();
263                 if(this.doc.body || this.doc.readyState == 'complete'){
264                     try {
265                         this.doc.designMode="on";
266                         
267                     } catch (e) {
268                         return;
269                     }
270                     Roo.TaskMgr.stop(task);
271                     this.initEditor.defer(10, this);
272                 }
273             },
274             interval : 10,
275             duration: 10000,
276             scope: this
277         };
278         Roo.TaskMgr.start(task);
279
280     },
281
282     // private
283     onResize : function(w, h)
284     {
285          Roo.log('resize: ' +w + ',' + h );
286         //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
287         if(!this.iframe){
288             return;
289         }
290         if(typeof w == 'number'){
291             
292             this.iframe.style.width = w + 'px';
293         }
294         if(typeof h == 'number'){
295             
296             this.iframe.style.height = h + 'px';
297             if(this.doc){
298                 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
299             }
300         }
301         
302     },
303
304     /**
305      * Toggles the editor between standard and source edit mode.
306      * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
307      */
308     toggleSourceEdit : function(sourceEditMode){
309         
310         this.sourceEditMode = sourceEditMode === true;
311         
312         if(this.sourceEditMode){
313  
314             Roo.get(this.iframe).addClass(['x-hidden','hide', 'd-none']);     //FIXME - what's the BS styles for these
315             
316         }else{
317             Roo.get(this.iframe).removeClass(['x-hidden','hide', 'd-none']);
318             //this.iframe.className = '';
319             this.deferFocus();
320         }
321         //this.setSize(this.owner.wrap.getSize());
322         //this.fireEvent('editmodechange', this, this.sourceEditMode);
323     },
324
325     
326   
327
328     /**
329      * Protected method that will not generally be called directly. If you need/want
330      * custom HTML cleanup, this is the method you should override.
331      * @param {String} html The HTML to be cleaned
332      * return {String} The cleaned HTML
333      */
334     cleanHtml : function(html)
335     {
336         html = String(html);
337         if(html.length > 5){
338             if(Roo.isSafari){ // strip safari nonsense
339                 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
340             }
341         }
342         if(html == '&nbsp;'){
343             html = '';
344         }
345         return html;
346     },
347
348     /**
349      * HTML Editor -> Textarea
350      * Protected method that will not generally be called directly. Syncs the contents
351      * of the editor iframe with the textarea.
352      */
353     syncValue : function()
354     {
355         //Roo.log("HtmlEditorCore:syncValue (EDITOR->TEXT)");
356         if(this.initialized){
357             
358             if (this.undoManager) {
359                 this.undoManager.addEvent();
360             }
361
362             
363             var bd = (this.doc.body || this.doc.documentElement);
364            
365             
366             var sel = this.win.getSelection();
367             
368             var div = document.createElement('div');
369             div.innerHTML = bd.innerHTML;
370             var gtx = div.getElementsByClassName('gtx-trans-icon'); // google translate - really annoying and difficult to get rid of.
371             if (gtx.length > 0) {
372                 var rm = gtx.item(0).parentNode;
373                 rm.parentNode.removeChild(rm);
374             }
375             
376            
377             if (this.enableBlocks) {
378                 new Roo.htmleditor.FilterBlock({ node : div });
379             }
380             //?? tidy?
381             if (this.autoClean) {
382                 var tidy = new Roo.htmleditor.TidySerializer({
383                     inner:  true
384                 });
385                 var html  = tidy.serialize(div);
386                 
387             }
388             
389             
390             if(Roo.isSafari){
391                 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
392                 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
393                 if(m && m[1]){
394                     html = '<div style="'+m[0]+'">' + html + '</div>';
395                 }
396             }
397             html = this.cleanHtml(html);
398             // fix up the special chars.. normaly like back quotes in word...
399             // however we do not want to do this with chinese..
400             html = html.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u0080-\uFFFF]/g, function(match) {
401                 
402                 var cc = match.charCodeAt();
403
404                 // Get the character value, handling surrogate pairs
405                 if (match.length == 2) {
406                     // It's a surrogate pair, calculate the Unicode code point
407                     var high = match.charCodeAt(0) - 0xD800;
408                     var low  = match.charCodeAt(1) - 0xDC00;
409                     cc = (high * 0x400) + low + 0x10000;
410                 }  else if (
411                     (cc >= 0x4E00 && cc < 0xA000 ) ||
412                     (cc >= 0x3400 && cc < 0x4E00 ) ||
413                     (cc >= 0xf900 && cc < 0xfb00 )
414                 ) {
415                         return match;
416                 }  
417          
418                 // No, use a numeric entity. Here we brazenly (and possibly mistakenly)
419                 return "&#" + cc + ";";
420                 
421                 
422             });
423             
424             
425              
426             if(this.owner.fireEvent('beforesync', this, html) !== false){
427                 this.el.dom.value = html;
428                 this.owner.fireEvent('sync', this, html);
429             }
430         }
431     },
432
433     /**
434      * TEXTAREA -> EDITABLE
435      * Protected method that will not generally be called directly. Pushes the value of the textarea
436      * into the iframe editor.
437      */
438     pushValue : function()
439     {
440         //Roo.log("HtmlEditorCore:pushValue (TEXT->EDITOR)");
441         if(this.initialized){
442             var v = this.el.dom.value.trim();
443             
444             
445             if(this.owner.fireEvent('beforepush', this, v) !== false){
446                 var d = (this.doc.body || this.doc.documentElement);
447                 d.innerHTML = v;
448                  
449                 this.el.dom.value = d.innerHTML;
450                 this.owner.fireEvent('push', this, v);
451             }
452             if (this.autoClean) {
453                 new Roo.htmleditor.FilterParagraph({node : this.doc.body}); // paragraphs
454                 new Roo.htmleditor.FilterSpan({node : this.doc.body}); // empty spans
455             }
456             if (this.enableBlocks) {
457                 Roo.htmleditor.Block.initAll(this.doc.body);
458             }
459             
460             this.updateLanguage();
461             
462             var lc = this.doc.body.lastChild;
463             if (lc && lc.nodeType == 1 && lc.getAttribute("contenteditable") == "false") {
464                 // add an extra line at the end.
465                 this.doc.body.appendChild(this.doc.createElement('br'));
466             }
467             
468             
469         }
470     },
471
472     // private
473     deferFocus : function(){
474         this.focus.defer(10, this);
475     },
476
477     // doc'ed in Field
478     focus : function(){
479         if(this.win && !this.sourceEditMode){
480             this.win.focus();
481         }else{
482             this.el.focus();
483         }
484     },
485     
486     assignDocWin: function()
487     {
488         var iframe = this.iframe;
489         
490          if(Roo.isIE){
491             this.doc = iframe.contentWindow.document;
492             this.win = iframe.contentWindow;
493         } else {
494 //            if (!Roo.get(this.frameId)) {
495 //                return;
496 //            }
497 //            this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
498 //            this.win = Roo.get(this.frameId).dom.contentWindow;
499             
500             if (!Roo.get(this.frameId) && !iframe.contentDocument) {
501                 return;
502             }
503             
504             this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
505             this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
506         }
507     },
508     
509     // private
510     initEditor : function(){
511         //console.log("INIT EDITOR");
512         this.assignDocWin();
513         
514         
515         
516         this.doc.designMode="on";
517         this.doc.open();
518         this.doc.write(this.getDocMarkup());
519         this.doc.close();
520         
521         var dbody = (this.doc.body || this.doc.documentElement);
522         //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
523         // this copies styles from the containing element into thsi one..
524         // not sure why we need all of this..
525         //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
526         
527         //var ss = this.el.getStyles( 'background-image', 'background-repeat');
528         //ss['background-attachment'] = 'fixed'; // w3c
529         dbody.bgProperties = 'fixed'; // ie
530         dbody.setAttribute("translate", "no");
531         
532         //Roo.DomHelper.applyStyles(dbody, ss);
533         Roo.EventManager.on(this.doc, {
534              
535             'mouseup': this.onEditorEvent,
536             'dblclick': this.onEditorEvent,
537             'click': this.onEditorEvent,
538             'keyup': this.onEditorEvent,
539             
540             buffer:100,
541             scope: this
542         });
543         Roo.EventManager.on(this.doc, {
544             'paste': this.onPasteEvent,
545             scope : this
546         });
547         if(Roo.isGecko){
548             Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
549         }
550         //??? needed???
551         if(Roo.isIE || Roo.isSafari || Roo.isOpera){
552             Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
553         }
554         this.initialized = true;
555
556         
557         // initialize special key events - enter
558         new Roo.htmleditor.KeyEnter({core : this});
559         
560          
561         
562         this.owner.fireEvent('initialize', this);
563         this.pushValue();
564     },
565     // this is to prevent a href clicks resulting in a redirect?
566    
567     onPasteEvent : function(e,v)
568     {
569         // I think we better assume paste is going to be a dirty load of rubish from word..
570         
571         // even pasting into a 'email version' of this widget will have to clean up that mess.
572         var cd = (e.browserEvent.clipboardData || window.clipboardData);
573         
574         // check what type of paste - if it's an image, then handle it differently.
575         if (cd.files && cd.files.length > 0) {
576             // pasting images?
577             var urlAPI = (window.createObjectURL && window) || 
578                 (window.URL && URL.revokeObjectURL && URL) || 
579                 (window.webkitURL && webkitURL);
580     
581             var url = urlAPI.createObjectURL( cd.files[0]);
582             this.insertAtCursor('<img src=" + url + ">');
583             return false;
584         }
585         if (cd.types.indexOf('text/html') < 0 ) {
586             return false;
587         }
588         var images = [];
589         var html = cd.getData('text/html'); // clipboard event
590         if (cd.types.indexOf('text/rtf') > -1) {
591             var parser = new Roo.rtf.Parser(cd.getData('text/rtf'));
592             images = parser.doc ? parser.doc.getElementsByType('pict') : [];
593         }
594         //Roo.log(images);
595         //Roo.log(imgs);
596         // fixme..
597         images = images.filter(function(g) { return !g.path.match(/^rtf\/(head|pgdsctbl|listtable|footerf)/); }) // ignore headers/footers etc.
598                        .map(function(g) { return g.toDataURL(); })
599                        .filter(function(g) { return g != 'about:blank'; });
600         
601         //Roo.log(html);
602         html = this.cleanWordChars(html);
603         
604         var d = (new DOMParser().parseFromString(html, 'text/html')).body;
605         
606         
607         var sn = this.getParentElement();
608         // check if d contains a table, and prevent nesting??
609         //Roo.log(d.getElementsByTagName('table'));
610         //Roo.log(sn);
611         //Roo.log(sn.closest('table'));
612         if (d.getElementsByTagName('table').length && sn && sn.closest('table')) {
613             e.preventDefault();
614             this.insertAtCursor("You can not nest tables");
615             //Roo.log("prevent?"); // fixme - 
616             return false;
617         }
618         
619         
620         
621         if (images.length > 0) {
622             // replace all v:imagedata - with img.
623             var ar = Array.from(d.getElementsByTagName('v:imagedata'));
624             Roo.each(ar, function(node) {
625                 node.parentNode.insertBefore(d.ownerDocument.createElement('img'), node );
626                 node.parentNode.removeChild(node);
627             });
628             
629             
630             Roo.each(d.getElementsByTagName('img'), function(img, i) {
631                 img.setAttribute('src', images[i]);
632             });
633         }
634         if (this.autoClean) {
635             new Roo.htmleditor.FilterWord({ node : d });
636             
637             new Roo.htmleditor.FilterStyleToTag({ node : d });
638             new Roo.htmleditor.FilterAttributes({
639                 node : d,
640                 attrib_white : ['href', 'src', 'name', 'align', 'colspan', 'rowspan', 'data-display', 'data-width', 'start'],
641                 attrib_clean : ['href', 'src' ] 
642             });
643             new Roo.htmleditor.FilterBlack({ node : d, tag : this.black});
644             // should be fonts..
645             new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT', ':' ]} );
646             new Roo.htmleditor.FilterParagraph({ node : d });
647             new Roo.htmleditor.FilterSpan({ node : d });
648             new Roo.htmleditor.FilterLongBr({ node : d });
649             new Roo.htmleditor.FilterComment({ node : d });
650             
651             
652         }
653         if (this.enableBlocks) {
654                 
655             Array.from(d.getElementsByTagName('img')).forEach(function(img) {
656                 if (img.closest('figure')) { // assume!! that it's aready
657                     return;
658                 }
659                 var fig  = new Roo.htmleditor.BlockFigure({
660                     image_src  : img.src
661                 });
662                 fig.updateElement(img); // replace it..
663                 
664             });
665         }
666         
667         
668         this.insertAtCursor(d.innerHTML.replace(/&nbsp;/g,' '));
669         if (this.enableBlocks) {
670             Roo.htmleditor.Block.initAll(this.doc.body);
671         }
672          
673         
674         e.preventDefault();
675         return false;
676         // default behaveiour should be our local cleanup paste? (optional?)
677         // for simple editor - we want to hammer the paste and get rid of everything... - so over-rideable..
678         //this.owner.fireEvent('paste', e, v);
679     },
680     // private
681     onDestroy : function(){
682         
683         
684         
685         if(this.rendered){
686             
687             //for (var i =0; i < this.toolbars.length;i++) {
688             //    // fixme - ask toolbars for heights?
689             //    this.toolbars[i].onDestroy();
690            // }
691             
692             //this.wrap.dom.innerHTML = '';
693             //this.wrap.remove();
694         }
695     },
696
697     // private
698     onFirstFocus : function(){
699         
700         this.assignDocWin();
701         this.undoManager = new Roo.lib.UndoManager(100,(this.doc.body || this.doc.documentElement));
702         
703         this.activated = true;
704          
705     
706         if(Roo.isGecko){ // prevent silly gecko errors
707             this.win.focus();
708             var s = this.win.getSelection();
709             if(!s.focusNode || s.focusNode.nodeType != 3){
710                 var r = s.getRangeAt(0);
711                 r.selectNodeContents((this.doc.body || this.doc.documentElement));
712                 r.collapse(true);
713                 this.deferFocus();
714             }
715             try{
716                 this.execCmd('useCSS', true);
717                 this.execCmd('styleWithCSS', false);
718             }catch(e){}
719         }
720         this.owner.fireEvent('activate', this);
721     },
722
723     // private
724     adjustFont: function(btn){
725         var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
726         //if(Roo.isSafari){ // safari
727         //    adjust *= 2;
728        // }
729         var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
730         if(Roo.isSafari){ // safari
731             var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
732             v =  (v < 10) ? 10 : v;
733             v =  (v > 48) ? 48 : v;
734             v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
735             
736         }
737         
738         
739         v = Math.max(1, v+adjust);
740         
741         this.execCmd('FontSize', v  );
742     },
743
744     onEditorEvent : function(e)
745     {
746          
747         
748         if (e && (e.ctrlKey || e.metaKey) && e.keyCode === 90) {
749             return; // we do not handle this.. (undo manager does..)
750         }
751         // in theory this detects if the last element is not a br, then we try and do that.
752         // its so clicking in space at bottom triggers adding a br and moving the cursor.
753         if (e &&
754             e.target.nodeName == 'BODY' &&
755             e.type == "mouseup" &&
756             this.doc.body.lastChild
757            ) {
758             var lc = this.doc.body.lastChild;
759             // gtx-trans is google translate plugin adding crap.
760             while ((lc.nodeType == 3 && lc.nodeValue == '') || lc.id == 'gtx-trans') {
761                 lc = lc.previousSibling;
762             }
763             if (lc.nodeType == 1 && lc.nodeName != 'BR') {
764             // if last element is <BR> - then dont do anything.
765             
766                 var ns = this.doc.createElement('br');
767                 this.doc.body.appendChild(ns);
768                 range = this.doc.createRange();
769                 range.setStartAfter(ns);
770                 range.collapse(true);
771                 var sel = this.win.getSelection();
772                 sel.removeAllRanges();
773                 sel.addRange(range);
774             }
775         }
776         
777         
778         
779         this.fireEditorEvent(e);
780       //  this.updateToolbar();
781         this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
782     },
783     
784     fireEditorEvent: function(e)
785     {
786         this.owner.fireEvent('editorevent', this, e);
787     },
788
789     insertTag : function(tg)
790     {
791         // could be a bit smarter... -> wrap the current selected tRoo..
792         if (tg.toLowerCase() == 'span' ||
793             tg.toLowerCase() == 'code' ||
794             tg.toLowerCase() == 'sup' ||
795             tg.toLowerCase() == 'sub' 
796             ) {
797             
798             range = this.createRange(this.getSelection());
799             var wrappingNode = this.doc.createElement(tg.toLowerCase());
800             wrappingNode.appendChild(range.extractContents());
801             range.insertNode(wrappingNode);
802
803             return;
804             
805             
806             
807         }
808         this.execCmd("formatblock",   tg);
809         this.undoManager.addEvent(); 
810     },
811     
812     insertText : function(txt)
813     {
814         
815         
816         var range = this.createRange();
817         range.deleteContents();
818                //alert(Sender.getAttribute('label'));
819                
820         range.insertNode(this.doc.createTextNode(txt));
821         this.undoManager.addEvent();
822     } ,
823     
824      
825
826     /**
827      * Executes a Midas editor command on the editor document and performs necessary focus and
828      * toolbar updates. <b>This should only be called after the editor is initialized.</b>
829      * @param {String} cmd The Midas command
830      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
831      */
832     relayCmd : function(cmd, value)
833     {
834         
835         switch (cmd) {
836             case 'justifyleft':
837             case 'justifyright':
838             case 'justifycenter':
839                 // if we are in a cell, then we will adjust the
840                 var n = this.getParentElement();
841                 var td = n.closest('td');
842                 if (td) {
843                     var bl = Roo.htmleditor.Block.factory(td);
844                     bl.textAlign = cmd.replace('justify','');
845                     bl.updateElement();
846                     this.owner.fireEvent('editorevent', this);
847                     return;
848                 }
849                 this.execCmd('styleWithCSS', true); // 
850                 break;
851             case 'bold':
852             case 'italic':
853                 // if there is no selection, then we insert, and set the curson inside it..
854                 this.execCmd('styleWithCSS', false); 
855                 break;
856                 
857         
858             default:
859                 break;
860         }
861         
862         
863         this.win.focus();
864         this.execCmd(cmd, value);
865         this.owner.fireEvent('editorevent', this);
866         //this.updateToolbar();
867         this.owner.deferFocus();
868     },
869
870     /**
871      * Executes a Midas editor command directly on the editor document.
872      * For visual commands, you should use {@link #relayCmd} instead.
873      * <b>This should only be called after the editor is initialized.</b>
874      * @param {String} cmd The Midas command
875      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
876      */
877     execCmd : function(cmd, value){
878         this.doc.execCommand(cmd, false, value === undefined ? null : value);
879         this.syncValue();
880     },
881  
882  
883    
884     /**
885      * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
886      * to insert tRoo.
887      * @param {String} text | dom node.. 
888      */
889     insertAtCursor : function(text)
890     {
891         
892         if(!this.activated){
893             return;
894         }
895          
896         if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
897             this.win.focus();
898             
899             
900             // from jquery ui (MIT licenced)
901             var range, node;
902             var win = this.win;
903             
904             if (win.getSelection && win.getSelection().getRangeAt) {
905                 
906                 // delete the existing?
907                 
908                 this.createRange(this.getSelection()).deleteContents();
909                 range = win.getSelection().getRangeAt(0);
910                 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
911                 range.insertNode(node);
912                 range = range.cloneRange();
913                 range.collapse(false);
914                  
915                 win.getSelection().removeAllRanges();
916                 win.getSelection().addRange(range);
917                 
918                 
919                 
920             } else if (win.document.selection && win.document.selection.createRange) {
921                 // no firefox support
922                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
923                 win.document.selection.createRange().pasteHTML(txt);
924             
925             } else {
926                 // no firefox support
927                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
928                 this.execCmd('InsertHTML', txt);
929             } 
930             this.syncValue();
931             
932             this.deferFocus();
933         }
934     },
935  // private
936     mozKeyPress : function(e){
937         if(e.ctrlKey){
938             var c = e.getCharCode(), cmd;
939           
940             if(c > 0){
941                 c = String.fromCharCode(c).toLowerCase();
942                 switch(c){
943                     case 'b':
944                         cmd = 'bold';
945                         break;
946                     case 'i':
947                         cmd = 'italic';
948                         break;
949                     
950                     case 'u':
951                         cmd = 'underline';
952                         break;
953                     
954                     //case 'v':
955                       //  this.cleanUpPaste.defer(100, this);
956                       //  return;
957                         
958                 }
959                 if(cmd){
960                     
961                     this.relayCmd(cmd);
962                     //this.win.focus();
963                     //this.execCmd(cmd);
964                     //this.deferFocus();
965                     e.preventDefault();
966                 }
967                 
968             }
969         }
970     },
971
972     // private
973     fixKeys : function(){ // load time branching for fastest keydown performance
974         
975         
976         if(Roo.isIE){
977             return function(e){
978                 var k = e.getKey(), r;
979                 if(k == e.TAB){
980                     e.stopEvent();
981                     r = this.doc.selection.createRange();
982                     if(r){
983                         r.collapse(true);
984                         r.pasteHTML('&#160;&#160;&#160;&#160;');
985                         this.deferFocus();
986                     }
987                     return;
988                 }
989                 /// this is handled by Roo.htmleditor.KeyEnter
990                  /*
991                 if(k == e.ENTER){
992                     r = this.doc.selection.createRange();
993                     if(r){
994                         var target = r.parentElement();
995                         if(!target || target.tagName.toLowerCase() != 'li'){
996                             e.stopEvent();
997                             r.pasteHTML('<br/>');
998                             r.collapse(false);
999                             r.select();
1000                         }
1001                     }
1002                 }
1003                 */
1004                 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
1005                 //    this.cleanUpPaste.defer(100, this);
1006                 //    return;
1007                 //}
1008                 
1009                 
1010             };
1011         }else if(Roo.isOpera){
1012             return function(e){
1013                 var k = e.getKey();
1014                 if(k == e.TAB){
1015                     e.stopEvent();
1016                     this.win.focus();
1017                     this.execCmd('InsertHTML','&#160;&#160;&#160;&#160;');
1018                     this.deferFocus();
1019                 }
1020                
1021                 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
1022                 //    this.cleanUpPaste.defer(100, this);
1023                  //   return;
1024                 //}
1025                 
1026             };
1027         }else if(Roo.isSafari){
1028             return function(e){
1029                 var k = e.getKey();
1030                 
1031                 if(k == e.TAB){
1032                     e.stopEvent();
1033                     this.execCmd('InsertText','\t');
1034                     this.deferFocus();
1035                     return;
1036                 }
1037                  this.mozKeyPress(e);
1038                 
1039                //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
1040                  //   this.cleanUpPaste.defer(100, this);
1041                  //   return;
1042                // }
1043                 
1044              };
1045         }
1046     }(),
1047     
1048     getAllAncestors: function()
1049     {
1050         var p = this.getSelectedNode();
1051         var a = [];
1052         if (!p) {
1053             a.push(p); // push blank onto stack..
1054             p = this.getParentElement();
1055         }
1056         
1057         
1058         while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
1059             a.push(p);
1060             p = p.parentNode;
1061         }
1062         a.push(this.doc.body);
1063         return a;
1064     },
1065     lastSel : false,
1066     lastSelNode : false,
1067     
1068     
1069     getSelection : function() 
1070     {
1071         this.assignDocWin();
1072         return Roo.lib.Selection.wrap(Roo.isIE ? this.doc.selection : this.win.getSelection(), this.doc);
1073     },
1074     /**
1075      * Select a dom node
1076      * @param {DomElement} node the node to select
1077      */
1078     selectNode : function(node, collapse)
1079     {
1080         var nodeRange = node.ownerDocument.createRange();
1081         try {
1082             nodeRange.selectNode(node);
1083         } catch (e) {
1084             nodeRange.selectNodeContents(node);
1085         }
1086         if (collapse === true) {
1087             nodeRange.collapse(true);
1088         }
1089         //
1090         var s = this.win.getSelection();
1091         s.removeAllRanges();
1092         s.addRange(nodeRange);
1093     },
1094     
1095     getSelectedNode: function() 
1096     {
1097         // this may only work on Gecko!!!
1098         
1099         // should we cache this!!!!
1100         
1101          
1102          
1103         var range = this.createRange(this.getSelection()).cloneRange();
1104         
1105         if (Roo.isIE) {
1106             var parent = range.parentElement();
1107             while (true) {
1108                 var testRange = range.duplicate();
1109                 testRange.moveToElementText(parent);
1110                 if (testRange.inRange(range)) {
1111                     break;
1112                 }
1113                 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
1114                     break;
1115                 }
1116                 parent = parent.parentElement;
1117             }
1118             return parent;
1119         }
1120         
1121         // is ancestor a text element.
1122         var ac =  range.commonAncestorContainer;
1123         if (ac.nodeType == 3) {
1124             ac = ac.parentNode;
1125         }
1126         
1127         var ar = ac.childNodes;
1128          
1129         var nodes = [];
1130         var other_nodes = [];
1131         var has_other_nodes = false;
1132         for (var i=0;i<ar.length;i++) {
1133             if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ? 
1134                 continue;
1135             }
1136             // fullly contained node.
1137             
1138             if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
1139                 nodes.push(ar[i]);
1140                 continue;
1141             }
1142             
1143             // probably selected..
1144             if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
1145                 other_nodes.push(ar[i]);
1146                 continue;
1147             }
1148             // outer..
1149             if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0))  {
1150                 continue;
1151             }
1152             
1153             
1154             has_other_nodes = true;
1155         }
1156         if (!nodes.length && other_nodes.length) {
1157             nodes= other_nodes;
1158         }
1159         if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
1160             return false;
1161         }
1162         
1163         return nodes[0];
1164     },
1165     
1166     
1167     createRange: function(sel)
1168     {
1169         // this has strange effects when using with 
1170         // top toolbar - not sure if it's a great idea.
1171         //this.editor.contentWindow.focus();
1172         if (typeof sel != "undefined") {
1173             try {
1174                 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
1175             } catch(e) {
1176                 return this.doc.createRange();
1177             }
1178         } else {
1179             return this.doc.createRange();
1180         }
1181     },
1182     getParentElement: function()
1183     {
1184         
1185         this.assignDocWin();
1186         var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
1187         
1188         var range = this.createRange(sel);
1189          
1190         try {
1191             var p = range.commonAncestorContainer;
1192             while (p.nodeType == 3) { // text node
1193                 p = p.parentNode;
1194             }
1195             return p;
1196         } catch (e) {
1197             return null;
1198         }
1199     
1200     },
1201     /***
1202      *
1203      * Range intersection.. the hard stuff...
1204      *  '-1' = before
1205      *  '0' = hits..
1206      *  '1' = after.
1207      *         [ -- selected range --- ]
1208      *   [fail]                        [fail]
1209      *
1210      *    basically..
1211      *      if end is before start or  hits it. fail.
1212      *      if start is after end or hits it fail.
1213      *
1214      *   if either hits (but other is outside. - then it's not 
1215      *   
1216      *    
1217      **/
1218     
1219     
1220     // @see http://www.thismuchiknow.co.uk/?p=64.
1221     rangeIntersectsNode : function(range, node)
1222     {
1223         var nodeRange = node.ownerDocument.createRange();
1224         try {
1225             nodeRange.selectNode(node);
1226         } catch (e) {
1227             nodeRange.selectNodeContents(node);
1228         }
1229     
1230         var rangeStartRange = range.cloneRange();
1231         rangeStartRange.collapse(true);
1232     
1233         var rangeEndRange = range.cloneRange();
1234         rangeEndRange.collapse(false);
1235     
1236         var nodeStartRange = nodeRange.cloneRange();
1237         nodeStartRange.collapse(true);
1238     
1239         var nodeEndRange = nodeRange.cloneRange();
1240         nodeEndRange.collapse(false);
1241     
1242         return rangeStartRange.compareBoundaryPoints(
1243                  Range.START_TO_START, nodeEndRange) == -1 &&
1244                rangeEndRange.compareBoundaryPoints(
1245                  Range.START_TO_START, nodeStartRange) == 1;
1246         
1247          
1248     },
1249     rangeCompareNode : function(range, node)
1250     {
1251         var nodeRange = node.ownerDocument.createRange();
1252         try {
1253             nodeRange.selectNode(node);
1254         } catch (e) {
1255             nodeRange.selectNodeContents(node);
1256         }
1257         
1258         
1259         range.collapse(true);
1260     
1261         nodeRange.collapse(true);
1262      
1263         var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1264         var ee = range.compareBoundaryPoints(  Range.END_TO_END, nodeRange);
1265          
1266         //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1267         
1268         var nodeIsBefore   =  ss == 1;
1269         var nodeIsAfter    = ee == -1;
1270         
1271         if (nodeIsBefore && nodeIsAfter) {
1272             return 0; // outer
1273         }
1274         if (!nodeIsBefore && nodeIsAfter) {
1275             return 1; //right trailed.
1276         }
1277         
1278         if (nodeIsBefore && !nodeIsAfter) {
1279             return 2;  // left trailed.
1280         }
1281         // fully contined.
1282         return 3;
1283     },
1284  
1285     cleanWordChars : function(input) {// change the chars to hex code
1286         
1287        var swapCodes  = [ 
1288             [    8211, "&#8211;" ], 
1289             [    8212, "&#8212;" ], 
1290             [    8216,  "'" ],  
1291             [    8217, "'" ],  
1292             [    8220, '"' ],  
1293             [    8221, '"' ],  
1294             [    8226, "*" ],  
1295             [    8230, "..." ]
1296         ]; 
1297         var output = input;
1298         Roo.each(swapCodes, function(sw) { 
1299             var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1300             
1301             output = output.replace(swapper, sw[1]);
1302         });
1303         
1304         return output;
1305     },
1306     
1307      
1308     
1309         
1310     
1311     cleanUpChild : function (node)
1312     {
1313         
1314         new Roo.htmleditor.FilterComment({node : node});
1315         new Roo.htmleditor.FilterAttributes({
1316                 node : node,
1317                 attrib_black : this.ablack,
1318                 attrib_clean : this.aclean,
1319                 style_white : this.cwhite,
1320                 style_black : this.cblack
1321         });
1322         new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
1323         new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
1324          
1325         
1326     },
1327     
1328     /**
1329      * Clean up MS wordisms...
1330      * @deprecated - use filter directly
1331      */
1332     cleanWord : function(node)
1333     {
1334         new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
1335         new Roo.htmleditor.FilterKeepChildren({node : node ? node : this.doc.body, tag : [ 'FONT', ':' ]} );
1336         
1337     },
1338    
1339     
1340     /**
1341
1342      * @deprecated - use filters
1343      */
1344     cleanTableWidths : function(node)
1345     {
1346         new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
1347         
1348  
1349     },
1350     
1351      
1352         
1353     applyBlacklists : function()
1354     {
1355         var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white  : [];
1356         var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black :  [];
1357         
1358         this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean :  Roo.HtmlEditorCore.aclean;
1359         this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack :  Roo.HtmlEditorCore.ablack;
1360         this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove :  Roo.HtmlEditorCore.tag_remove;
1361         
1362         this.white = [];
1363         this.black = [];
1364         Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1365             if (b.indexOf(tag) > -1) {
1366                 return;
1367             }
1368             this.white.push(tag);
1369             
1370         }, this);
1371         
1372         Roo.each(w, function(tag) {
1373             if (b.indexOf(tag) > -1) {
1374                 return;
1375             }
1376             if (this.white.indexOf(tag) > -1) {
1377                 return;
1378             }
1379             this.white.push(tag);
1380             
1381         }, this);
1382         
1383         
1384         Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1385             if (w.indexOf(tag) > -1) {
1386                 return;
1387             }
1388             this.black.push(tag);
1389             
1390         }, this);
1391         
1392         Roo.each(b, function(tag) {
1393             if (w.indexOf(tag) > -1) {
1394                 return;
1395             }
1396             if (this.black.indexOf(tag) > -1) {
1397                 return;
1398             }
1399             this.black.push(tag);
1400             
1401         }, this);
1402         
1403         
1404         w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite  : [];
1405         b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack :  [];
1406         
1407         this.cwhite = [];
1408         this.cblack = [];
1409         Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1410             if (b.indexOf(tag) > -1) {
1411                 return;
1412             }
1413             this.cwhite.push(tag);
1414             
1415         }, this);
1416         
1417         Roo.each(w, function(tag) {
1418             if (b.indexOf(tag) > -1) {
1419                 return;
1420             }
1421             if (this.cwhite.indexOf(tag) > -1) {
1422                 return;
1423             }
1424             this.cwhite.push(tag);
1425             
1426         }, this);
1427         
1428         
1429         Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1430             if (w.indexOf(tag) > -1) {
1431                 return;
1432             }
1433             this.cblack.push(tag);
1434             
1435         }, this);
1436         
1437         Roo.each(b, function(tag) {
1438             if (w.indexOf(tag) > -1) {
1439                 return;
1440             }
1441             if (this.cblack.indexOf(tag) > -1) {
1442                 return;
1443             }
1444             this.cblack.push(tag);
1445             
1446         }, this);
1447     },
1448     
1449     setStylesheets : function(stylesheets)
1450     {
1451         if(typeof(stylesheets) == 'string'){
1452             Roo.get(this.iframe.contentDocument.head).createChild({
1453                 tag : 'link',
1454                 rel : 'stylesheet',
1455                 type : 'text/css',
1456                 href : stylesheets
1457             });
1458             
1459             return;
1460         }
1461         var _this = this;
1462      
1463         Roo.each(stylesheets, function(s) {
1464             if(!s.length){
1465                 return;
1466             }
1467             
1468             Roo.get(_this.iframe.contentDocument.head).createChild({
1469                 tag : 'link',
1470                 rel : 'stylesheet',
1471                 type : 'text/css',
1472                 href : s
1473             });
1474         });
1475
1476         
1477     },
1478     
1479     
1480     updateLanguage : function()
1481     {
1482         if (!this.iframe || !this.iframe.contentDocument) {
1483             return;
1484         }
1485         Roo.get(this.iframe.contentDocument.body).attr("lang", this.language);
1486     },
1487     
1488     
1489     removeStylesheets : function()
1490     {
1491         var _this = this;
1492         
1493         Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1494             s.remove();
1495         });
1496     },
1497     
1498     setStyle : function(style)
1499     {
1500         Roo.get(this.iframe.contentDocument.head).createChild({
1501             tag : 'style',
1502             type : 'text/css',
1503             html : style
1504         });
1505
1506         return;
1507     }
1508     
1509     // hide stuff that is not compatible
1510     /**
1511      * @event blur
1512      * @hide
1513      */
1514     /**
1515      * @event change
1516      * @hide
1517      */
1518     /**
1519      * @event focus
1520      * @hide
1521      */
1522     /**
1523      * @event specialkey
1524      * @hide
1525      */
1526     /**
1527      * @cfg {String} fieldClass @hide
1528      */
1529     /**
1530      * @cfg {String} focusClass @hide
1531      */
1532     /**
1533      * @cfg {String} autoCreate @hide
1534      */
1535     /**
1536      * @cfg {String} inputType @hide
1537      */
1538     /**
1539      * @cfg {String} invalidClass @hide
1540      */
1541     /**
1542      * @cfg {String} invalidText @hide
1543      */
1544     /**
1545      * @cfg {String} msgFx @hide
1546      */
1547     /**
1548      * @cfg {String} validateOnBlur @hide
1549      */
1550 });
1551
1552 Roo.HtmlEditorCore.white = [
1553         'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
1554         
1555        'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD',      'DIR',       'DIV', 
1556        'DL',      'DT',         'H1',     'H2',      'H3',        'H4', 
1557        'H5',      'H6',         'HR',     'ISINDEX', 'LISTING',   'MARQUEE', 
1558        'MENU',    'MULTICOL',   'OL',     'P',       'PLAINTEXT', 'PRE', 
1559        'TABLE',   'UL',         'XMP', 
1560        
1561        'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH', 
1562       'THEAD',   'TR', 
1563      
1564       'DIR', 'MENU', 'OL', 'UL', 'DL',
1565        
1566       'EMBED',  'OBJECT'
1567 ];
1568
1569
1570 Roo.HtmlEditorCore.black = [
1571     //    'embed',  'object', // enable - backend responsiblity to clean thiese
1572         'APPLET', // 
1573         'BASE',   'BASEFONT', 'BGSOUND', 'BLINK',  'BODY', 
1574         'FRAME',  'FRAMESET', 'HEAD',    'HTML',   'ILAYER', 
1575         'IFRAME', 'LAYER',  'LINK',     'META',    'OBJECT',   
1576         'SCRIPT', 'STYLE' ,'TITLE',  'XML',
1577         //'FONT' // CLEAN LATER..
1578         'COLGROUP', 'COL'   // messy tables.
1579         
1580         
1581 ];
1582 Roo.HtmlEditorCore.clean = [ // ?? needed???
1583      'SCRIPT', 'STYLE', 'TITLE', 'XML'
1584 ];
1585 Roo.HtmlEditorCore.tag_remove = [
1586     'FONT', 'TBODY'  
1587 ];
1588 // attributes..
1589
1590 Roo.HtmlEditorCore.ablack = [
1591     'on'
1592 ];
1593     
1594 Roo.HtmlEditorCore.aclean = [ 
1595     'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc' 
1596 ];
1597
1598 // protocols..
1599 Roo.HtmlEditorCore.pwhite= [
1600         'http',  'https',  'mailto'
1601 ];
1602
1603 // white listed style attributes.
1604 Roo.HtmlEditorCore.cwhite= [
1605       //  'text-align', /// default is to allow most things..
1606       
1607          
1608 //        'font-size'//??
1609 ];
1610
1611 // black listed style attributes.
1612 Roo.HtmlEditorCore.cblack= [
1613       //  'font-size' -- this can be set by the project 
1614 ];
1615
1616
1617
1618
1619