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