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