fix the image paste correctly
[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         // in theory this detects if the last element is not a br, then we try and do that.
807         // its so clicking in space at bottom triggers adding a br and moving the cursor.
808         if (e &&
809             e.target.nodeName == 'BODY' &&
810             e.type == "mouseup" &&
811             this.doc.body.lastChild
812            ) {
813             var lc = this.doc.body.lastChild;
814             // gtx-trans is google translate plugin adding crap.
815             while ((lc.nodeType == 3 && lc.nodeValue == '') || lc.id == 'gtx-trans') {
816                 lc = lc.previousSibling;
817             }
818             if (lc.nodeType == 1 && lc.nodeName != 'BR') {
819             // if last element is <BR> - then dont do anything.
820             
821                 var ns = this.doc.createElement('br');
822                 this.doc.body.appendChild(ns);
823                 range = this.doc.createRange();
824                 range.setStartAfter(ns);
825                 range.collapse(true);
826                 var sel = this.win.getSelection();
827                 sel.removeAllRanges();
828                 sel.addRange(range);
829             }
830         }
831         
832         
833         
834         this.fireEditorEvent(e);
835       //  this.updateToolbar();
836         this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
837     },
838     
839     fireEditorEvent: function(e)
840     {
841         this.owner.fireEvent('editorevent', this, e);
842     },
843
844     insertTag : function(tg)
845     {
846         // could be a bit smarter... -> wrap the current selected tRoo..
847         if (tg.toLowerCase() == 'span' ||
848             tg.toLowerCase() == 'code' ||
849             tg.toLowerCase() == 'sup' ||
850             tg.toLowerCase() == 'sub' 
851             ) {
852             
853             range = this.createRange(this.getSelection());
854             var wrappingNode = this.doc.createElement(tg.toLowerCase());
855             wrappingNode.appendChild(range.extractContents());
856             range.insertNode(wrappingNode);
857
858             return;
859             
860             
861             
862         }
863         this.execCmd("formatblock",   tg);
864         this.undoManager.addEvent(); 
865     },
866     
867     insertText : function(txt)
868     {
869         
870         
871         var range = this.createRange();
872         range.deleteContents();
873                //alert(Sender.getAttribute('label'));
874                
875         range.insertNode(this.doc.createTextNode(txt));
876         this.undoManager.addEvent();
877     } ,
878     
879      
880
881     /**
882      * Executes a Midas editor command on the editor document and performs necessary focus and
883      * toolbar updates. <b>This should only be called after the editor is initialized.</b>
884      * @param {String} cmd The Midas command
885      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
886      */
887     relayCmd : function(cmd, value)
888     {
889         
890         switch (cmd) {
891             case 'justifyleft':
892             case 'justifyright':
893             case 'justifycenter':
894                 // if we are in a cell, then we will adjust the
895                 var n = this.getParentElement();
896                 var td = n.closest('td');
897                 if (td) {
898                     var bl = Roo.htmleditor.Block.factory(td);
899                     bl.textAlign = cmd.replace('justify','');
900                     bl.updateElement();
901                     this.owner.fireEvent('editorevent', this);
902                     return;
903                 }
904                 this.execCmd('styleWithCSS', true); // 
905                 break;
906             case 'bold':
907             case 'italic':
908                 // if there is no selection, then we insert, and set the curson inside it..
909                 this.execCmd('styleWithCSS', false); 
910                 break;
911                 
912         
913             default:
914                 break;
915         }
916         
917         
918         this.win.focus();
919         this.execCmd(cmd, value);
920         this.owner.fireEvent('editorevent', this);
921         //this.updateToolbar();
922         this.owner.deferFocus();
923     },
924
925     /**
926      * Executes a Midas editor command directly on the editor document.
927      * For visual commands, you should use {@link #relayCmd} instead.
928      * <b>This should only be called after the editor is initialized.</b>
929      * @param {String} cmd The Midas command
930      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
931      */
932     execCmd : function(cmd, value){
933         this.doc.execCommand(cmd, false, value === undefined ? null : value);
934         this.syncValue();
935     },
936  
937  
938    
939     /**
940      * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
941      * to insert tRoo.
942      * @param {String} text | dom node.. 
943      */
944     insertAtCursor : function(text)
945     {
946         
947         if(!this.activated){
948             return;
949         }
950          
951         if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
952             this.win.focus();
953             
954             
955             // from jquery ui (MIT licenced)
956             var range, node;
957             var win = this.win;
958             
959             if (win.getSelection && win.getSelection().getRangeAt) {
960                 
961                 // delete the existing?
962                 
963                 this.createRange(this.getSelection()).deleteContents();
964                 range = win.getSelection().getRangeAt(0);
965                 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
966                 range.insertNode(node);
967                 range = range.cloneRange();
968                 range.collapse(false);
969                  
970                 win.getSelection().removeAllRanges();
971                 win.getSelection().addRange(range);
972                 
973                 
974                 
975             } else if (win.document.selection && win.document.selection.createRange) {
976                 // no firefox support
977                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
978                 win.document.selection.createRange().pasteHTML(txt);
979             
980             } else {
981                 // no firefox support
982                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
983                 this.execCmd('InsertHTML', txt);
984             } 
985             this.syncValue();
986             
987             this.deferFocus();
988         }
989     },
990  // private
991     mozKeyPress : function(e){
992         if(e.ctrlKey){
993             var c = e.getCharCode(), cmd;
994           
995             if(c > 0){
996                 c = String.fromCharCode(c).toLowerCase();
997                 switch(c){
998                     case 'b':
999                         cmd = 'bold';
1000                         break;
1001                     case 'i':
1002                         cmd = 'italic';
1003                         break;
1004                     
1005                     case 'u':
1006                         cmd = 'underline';
1007                         break;
1008                     
1009                     //case 'v':
1010                       //  this.cleanUpPaste.defer(100, this);
1011                       //  return;
1012                         
1013                 }
1014                 if(cmd){
1015                     
1016                     this.relayCmd(cmd);
1017                     //this.win.focus();
1018                     //this.execCmd(cmd);
1019                     //this.deferFocus();
1020                     e.preventDefault();
1021                 }
1022                 
1023             }
1024         }
1025     },
1026
1027     // private
1028     fixKeys : function(){ // load time branching for fastest keydown performance
1029         
1030         
1031         if(Roo.isIE){
1032             return function(e){
1033                 var k = e.getKey(), r;
1034                 if(k == e.TAB){
1035                     e.stopEvent();
1036                     r = this.doc.selection.createRange();
1037                     if(r){
1038                         r.collapse(true);
1039                         r.pasteHTML('&#160;&#160;&#160;&#160;');
1040                         this.deferFocus();
1041                     }
1042                     return;
1043                 }
1044                 /// this is handled by Roo.htmleditor.KeyEnter
1045                  /*
1046                 if(k == e.ENTER){
1047                     r = this.doc.selection.createRange();
1048                     if(r){
1049                         var target = r.parentElement();
1050                         if(!target || target.tagName.toLowerCase() != 'li'){
1051                             e.stopEvent();
1052                             r.pasteHTML('<br/>');
1053                             r.collapse(false);
1054                             r.select();
1055                         }
1056                     }
1057                 }
1058                 */
1059                 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
1060                 //    this.cleanUpPaste.defer(100, this);
1061                 //    return;
1062                 //}
1063                 
1064                 
1065             };
1066         }else if(Roo.isOpera){
1067             return function(e){
1068                 var k = e.getKey();
1069                 if(k == e.TAB){
1070                     e.stopEvent();
1071                     this.win.focus();
1072                     this.execCmd('InsertHTML','&#160;&#160;&#160;&#160;');
1073                     this.deferFocus();
1074                 }
1075                
1076                 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
1077                 //    this.cleanUpPaste.defer(100, this);
1078                  //   return;
1079                 //}
1080                 
1081             };
1082         }else if(Roo.isSafari){
1083             return function(e){
1084                 var k = e.getKey();
1085                 
1086                 if(k == e.TAB){
1087                     e.stopEvent();
1088                     this.execCmd('InsertText','\t');
1089                     this.deferFocus();
1090                     return;
1091                 }
1092                  this.mozKeyPress(e);
1093                 
1094                //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
1095                  //   this.cleanUpPaste.defer(100, this);
1096                  //   return;
1097                // }
1098                 
1099              };
1100         }
1101     }(),
1102     
1103     getAllAncestors: function()
1104     {
1105         var p = this.getSelectedNode();
1106         var a = [];
1107         if (!p) {
1108             a.push(p); // push blank onto stack..
1109             p = this.getParentElement();
1110         }
1111         
1112         
1113         while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
1114             a.push(p);
1115             p = p.parentNode;
1116         }
1117         a.push(this.doc.body);
1118         return a;
1119     },
1120     lastSel : false,
1121     lastSelNode : false,
1122     
1123     
1124     getSelection : function() 
1125     {
1126         this.assignDocWin();
1127         return Roo.lib.Selection.wrap(Roo.isIE ? this.doc.selection : this.win.getSelection(), this.doc);
1128     },
1129     /**
1130      * Select a dom node
1131      * @param {DomElement} node the node to select
1132      */
1133     selectNode : function(node, collapse)
1134     {
1135         var nodeRange = node.ownerDocument.createRange();
1136         try {
1137             nodeRange.selectNode(node);
1138         } catch (e) {
1139             nodeRange.selectNodeContents(node);
1140         }
1141         if (collapse === true) {
1142             nodeRange.collapse(true);
1143         }
1144         //
1145         var s = this.win.getSelection();
1146         s.removeAllRanges();
1147         s.addRange(nodeRange);
1148     },
1149     
1150     getSelectedNode: function() 
1151     {
1152         // this may only work on Gecko!!!
1153         
1154         // should we cache this!!!!
1155         
1156          
1157          
1158         var range = this.createRange(this.getSelection()).cloneRange();
1159         
1160         if (Roo.isIE) {
1161             var parent = range.parentElement();
1162             while (true) {
1163                 var testRange = range.duplicate();
1164                 testRange.moveToElementText(parent);
1165                 if (testRange.inRange(range)) {
1166                     break;
1167                 }
1168                 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
1169                     break;
1170                 }
1171                 parent = parent.parentElement;
1172             }
1173             return parent;
1174         }
1175         
1176         // is ancestor a text element.
1177         var ac =  range.commonAncestorContainer;
1178         if (ac.nodeType == 3) {
1179             ac = ac.parentNode;
1180         }
1181         
1182         var ar = ac.childNodes;
1183          
1184         var nodes = [];
1185         var other_nodes = [];
1186         var has_other_nodes = false;
1187         for (var i=0;i<ar.length;i++) {
1188             if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ? 
1189                 continue;
1190             }
1191             // fullly contained node.
1192             
1193             if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
1194                 nodes.push(ar[i]);
1195                 continue;
1196             }
1197             
1198             // probably selected..
1199             if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
1200                 other_nodes.push(ar[i]);
1201                 continue;
1202             }
1203             // outer..
1204             if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0))  {
1205                 continue;
1206             }
1207             
1208             
1209             has_other_nodes = true;
1210         }
1211         if (!nodes.length && other_nodes.length) {
1212             nodes= other_nodes;
1213         }
1214         if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
1215             return false;
1216         }
1217         
1218         return nodes[0];
1219     },
1220     
1221     
1222     createRange: function(sel)
1223     {
1224         // this has strange effects when using with 
1225         // top toolbar - not sure if it's a great idea.
1226         //this.editor.contentWindow.focus();
1227         if (typeof sel != "undefined") {
1228             try {
1229                 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
1230             } catch(e) {
1231                 return this.doc.createRange();
1232             }
1233         } else {
1234             return this.doc.createRange();
1235         }
1236     },
1237     getParentElement: function()
1238     {
1239         
1240         this.assignDocWin();
1241         var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
1242         
1243         var range = this.createRange(sel);
1244          
1245         try {
1246             var p = range.commonAncestorContainer;
1247             while (p.nodeType == 3) { // text node
1248                 p = p.parentNode;
1249             }
1250             return p;
1251         } catch (e) {
1252             return null;
1253         }
1254     
1255     },
1256     /***
1257      *
1258      * Range intersection.. the hard stuff...
1259      *  '-1' = before
1260      *  '0' = hits..
1261      *  '1' = after.
1262      *         [ -- selected range --- ]
1263      *   [fail]                        [fail]
1264      *
1265      *    basically..
1266      *      if end is before start or  hits it. fail.
1267      *      if start is after end or hits it fail.
1268      *
1269      *   if either hits (but other is outside. - then it's not 
1270      *   
1271      *    
1272      **/
1273     
1274     
1275     // @see http://www.thismuchiknow.co.uk/?p=64.
1276     rangeIntersectsNode : function(range, node)
1277     {
1278         var nodeRange = node.ownerDocument.createRange();
1279         try {
1280             nodeRange.selectNode(node);
1281         } catch (e) {
1282             nodeRange.selectNodeContents(node);
1283         }
1284     
1285         var rangeStartRange = range.cloneRange();
1286         rangeStartRange.collapse(true);
1287     
1288         var rangeEndRange = range.cloneRange();
1289         rangeEndRange.collapse(false);
1290     
1291         var nodeStartRange = nodeRange.cloneRange();
1292         nodeStartRange.collapse(true);
1293     
1294         var nodeEndRange = nodeRange.cloneRange();
1295         nodeEndRange.collapse(false);
1296     
1297         return rangeStartRange.compareBoundaryPoints(
1298                  Range.START_TO_START, nodeEndRange) == -1 &&
1299                rangeEndRange.compareBoundaryPoints(
1300                  Range.START_TO_START, nodeStartRange) == 1;
1301         
1302          
1303     },
1304     rangeCompareNode : function(range, node)
1305     {
1306         var nodeRange = node.ownerDocument.createRange();
1307         try {
1308             nodeRange.selectNode(node);
1309         } catch (e) {
1310             nodeRange.selectNodeContents(node);
1311         }
1312         
1313         
1314         range.collapse(true);
1315     
1316         nodeRange.collapse(true);
1317      
1318         var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1319         var ee = range.compareBoundaryPoints(  Range.END_TO_END, nodeRange);
1320          
1321         //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1322         
1323         var nodeIsBefore   =  ss == 1;
1324         var nodeIsAfter    = ee == -1;
1325         
1326         if (nodeIsBefore && nodeIsAfter) {
1327             return 0; // outer
1328         }
1329         if (!nodeIsBefore && nodeIsAfter) {
1330             return 1; //right trailed.
1331         }
1332         
1333         if (nodeIsBefore && !nodeIsAfter) {
1334             return 2;  // left trailed.
1335         }
1336         // fully contined.
1337         return 3;
1338     },
1339  
1340     cleanWordChars : function(input) {// change the chars to hex code
1341         
1342        var swapCodes  = [ 
1343             [    8211, "&#8211;" ], 
1344             [    8212, "&#8212;" ], 
1345             [    8216,  "'" ],  
1346             [    8217, "'" ],  
1347             [    8220, '"' ],  
1348             [    8221, '"' ],  
1349             [    8226, "*" ],  
1350             [    8230, "..." ]
1351         ]; 
1352         var output = input;
1353         Roo.each(swapCodes, function(sw) { 
1354             var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1355             
1356             output = output.replace(swapper, sw[1]);
1357         });
1358         
1359         return output;
1360     },
1361     
1362      
1363     
1364         
1365     
1366     cleanUpChild : function (node)
1367     {
1368         
1369         new Roo.htmleditor.FilterComment({node : node});
1370         new Roo.htmleditor.FilterAttributes({
1371                 node : node,
1372                 attrib_black : this.ablack,
1373                 attrib_clean : this.aclean,
1374                 style_white : this.cwhite,
1375                 style_black : this.cblack
1376         });
1377         new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
1378         new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
1379          
1380         
1381     },
1382     
1383     /**
1384      * Clean up MS wordisms...
1385      * @deprecated - use filter directly
1386      */
1387     cleanWord : function(node)
1388     {
1389         new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
1390         new Roo.htmleditor.FilterKeepChildren({node : node ? node : this.doc.body, tag : [ 'FONT', ':' ]} );
1391         
1392     },
1393    
1394     
1395     /**
1396
1397      * @deprecated - use filters
1398      */
1399     cleanTableWidths : function(node)
1400     {
1401         new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
1402         
1403  
1404     },
1405     
1406      
1407         
1408     applyBlacklists : function()
1409     {
1410         var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white  : [];
1411         var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black :  [];
1412         
1413         this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean :  Roo.HtmlEditorCore.aclean;
1414         this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack :  Roo.HtmlEditorCore.ablack;
1415         this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove :  Roo.HtmlEditorCore.tag_remove;
1416         
1417         this.white = [];
1418         this.black = [];
1419         Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1420             if (b.indexOf(tag) > -1) {
1421                 return;
1422             }
1423             this.white.push(tag);
1424             
1425         }, this);
1426         
1427         Roo.each(w, function(tag) {
1428             if (b.indexOf(tag) > -1) {
1429                 return;
1430             }
1431             if (this.white.indexOf(tag) > -1) {
1432                 return;
1433             }
1434             this.white.push(tag);
1435             
1436         }, this);
1437         
1438         
1439         Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1440             if (w.indexOf(tag) > -1) {
1441                 return;
1442             }
1443             this.black.push(tag);
1444             
1445         }, this);
1446         
1447         Roo.each(b, function(tag) {
1448             if (w.indexOf(tag) > -1) {
1449                 return;
1450             }
1451             if (this.black.indexOf(tag) > -1) {
1452                 return;
1453             }
1454             this.black.push(tag);
1455             
1456         }, this);
1457         
1458         
1459         w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite  : [];
1460         b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack :  [];
1461         
1462         this.cwhite = [];
1463         this.cblack = [];
1464         Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1465             if (b.indexOf(tag) > -1) {
1466                 return;
1467             }
1468             this.cwhite.push(tag);
1469             
1470         }, this);
1471         
1472         Roo.each(w, function(tag) {
1473             if (b.indexOf(tag) > -1) {
1474                 return;
1475             }
1476             if (this.cwhite.indexOf(tag) > -1) {
1477                 return;
1478             }
1479             this.cwhite.push(tag);
1480             
1481         }, this);
1482         
1483         
1484         Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1485             if (w.indexOf(tag) > -1) {
1486                 return;
1487             }
1488             this.cblack.push(tag);
1489             
1490         }, this);
1491         
1492         Roo.each(b, function(tag) {
1493             if (w.indexOf(tag) > -1) {
1494                 return;
1495             }
1496             if (this.cblack.indexOf(tag) > -1) {
1497                 return;
1498             }
1499             this.cblack.push(tag);
1500             
1501         }, this);
1502     },
1503     
1504     setStylesheets : function(stylesheets)
1505     {
1506         if(typeof(stylesheets) == 'string'){
1507             Roo.get(this.iframe.contentDocument.head).createChild({
1508                 tag : 'link',
1509                 rel : 'stylesheet',
1510                 type : 'text/css',
1511                 href : stylesheets
1512             });
1513             
1514             return;
1515         }
1516         var _this = this;
1517      
1518         Roo.each(stylesheets, function(s) {
1519             if(!s.length){
1520                 return;
1521             }
1522             
1523             Roo.get(_this.iframe.contentDocument.head).createChild({
1524                 tag : 'link',
1525                 rel : 'stylesheet',
1526                 type : 'text/css',
1527                 href : s
1528             });
1529         });
1530
1531         
1532     },
1533     
1534     
1535     updateLanguage : function()
1536     {
1537         if (!this.iframe || !this.iframe.contentDocument) {
1538             return;
1539         }
1540         Roo.get(this.iframe.contentDocument.body).attr("lang", this.language);
1541     },
1542     
1543     
1544     removeStylesheets : function()
1545     {
1546         var _this = this;
1547         
1548         Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1549             s.remove();
1550         });
1551     },
1552     
1553     setStyle : function(style)
1554     {
1555         Roo.get(this.iframe.contentDocument.head).createChild({
1556             tag : 'style',
1557             type : 'text/css',
1558             html : style
1559         });
1560
1561         return;
1562     }
1563     
1564     // hide stuff that is not compatible
1565     /**
1566      * @event blur
1567      * @hide
1568      */
1569     /**
1570      * @event change
1571      * @hide
1572      */
1573     /**
1574      * @event focus
1575      * @hide
1576      */
1577     /**
1578      * @event specialkey
1579      * @hide
1580      */
1581     /**
1582      * @cfg {String} fieldClass @hide
1583      */
1584     /**
1585      * @cfg {String} focusClass @hide
1586      */
1587     /**
1588      * @cfg {String} autoCreate @hide
1589      */
1590     /**
1591      * @cfg {String} inputType @hide
1592      */
1593     /**
1594      * @cfg {String} invalidClass @hide
1595      */
1596     /**
1597      * @cfg {String} invalidText @hide
1598      */
1599     /**
1600      * @cfg {String} msgFx @hide
1601      */
1602     /**
1603      * @cfg {String} validateOnBlur @hide
1604      */
1605 });
1606
1607 Roo.HtmlEditorCore.white = [
1608         'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
1609         
1610        'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD',      'DIR',       'DIV', 
1611        'DL',      'DT',         'H1',     'H2',      'H3',        'H4', 
1612        'H5',      'H6',         'HR',     'ISINDEX', 'LISTING',   'MARQUEE', 
1613        'MENU',    'MULTICOL',   'OL',     'P',       'PLAINTEXT', 'PRE', 
1614        'TABLE',   'UL',         'XMP', 
1615        
1616        'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH', 
1617       'THEAD',   'TR', 
1618      
1619       'DIR', 'MENU', 'OL', 'UL', 'DL',
1620        
1621       'EMBED',  'OBJECT'
1622 ];
1623
1624
1625 Roo.HtmlEditorCore.black = [
1626     //    'embed',  'object', // enable - backend responsiblity to clean thiese
1627         'APPLET', // 
1628         'BASE',   'BASEFONT', 'BGSOUND', 'BLINK',  'BODY', 
1629         'FRAME',  'FRAMESET', 'HEAD',    'HTML',   'ILAYER', 
1630         'IFRAME', 'LAYER',  'LINK',     'META',    'OBJECT',   
1631         'SCRIPT', 'STYLE' ,'TITLE',  'XML',
1632         //'FONT' // CLEAN LATER..
1633         'COLGROUP', 'COL'   // messy tables.
1634         
1635         
1636 ];
1637 Roo.HtmlEditorCore.clean = [ // ?? needed???
1638      'SCRIPT', 'STYLE', 'TITLE', 'XML'
1639 ];
1640 Roo.HtmlEditorCore.tag_remove = [
1641     'FONT', 'TBODY'  
1642 ];
1643 // attributes..
1644
1645 Roo.HtmlEditorCore.ablack = [
1646     'on'
1647 ];
1648     
1649 Roo.HtmlEditorCore.aclean = [ 
1650     'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc' 
1651 ];
1652
1653 // protocols..
1654 Roo.HtmlEditorCore.pwhite= [
1655         'http',  'https',  'mailto'
1656 ];
1657
1658 // white listed style attributes.
1659 Roo.HtmlEditorCore.cwhite= [
1660       //  'text-align', /// default is to allow most things..
1661       
1662          
1663 //        'font-size'//??
1664 ];
1665
1666 // black listed style attributes.
1667 Roo.HtmlEditorCore.cblack= [
1668       //  'font-size' -- this can be set by the project 
1669 ];
1670
1671
1672
1673
1674