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