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