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