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