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