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