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