fix broken commit
[roojs1] / roojs-bootstrap-debug.js
index 3e1b268..4d87cb6 100644 (file)
@@ -1,4 +1,4 @@
-/**
+Roo.bootstrap = {};/**
  * set the version of bootstrap based on the stylesheet...
  *
  */
@@ -17,7 +17,10 @@ Roo.bootstrap.version = ( function() {
 })(); Roo.bootstrap.menu = Roo.bootstrap.menu || {};
 Roo.bootstrap.nav = {};
 
-Roo.bootstrap.form = {};Roo.bootstrap.panel = {};Roo.bootstrap.layout = {};/*
+Roo.bootstrap.form = {};Roo.bootstrap.panel = {};Roo.bootstrap.layout = {};
+Roo.htmleditor = {};
+Roo.namespace('Roo.bootstrap.form.HtmlEditorToolbar');
+/*
  * Based on:
  * Ext JS Library 1.1.1
  * Copyright(c) 2006-2007, Ext JS, LLC.
@@ -1340,7 +1343,7 @@ Roo.extend(Roo.bootstrap.Button, Roo.bootstrap.Component,  {
         }
         
         Roo.log('button on click ');
-        if(this.preventDefault){
+        if(this.href === '' || this.preventDefault){
             e.preventDefault();
         }
         
@@ -2935,22 +2938,12 @@ Roo.extend(Roo.bootstrap.ButtonUploader, Roo.bootstrap.Button,  {
     
     getAutoCreate : function()
     {
-        var im = {
-            tag: 'input',
-            type : 'file',
-            cls : 'd-none  roo-card-upload-selector' 
-          
-        };
-        if (this.multiple) {
-            im.multiple = 'multiple';
-        }
+       
         
         return  {
             cls :'div' ,
             cn : [
-                Roo.bootstrap.Button.prototype.getAutoCreate.call(this),
-                im
-
+                Roo.bootstrap.Button.prototype.getAutoCreate.call(this) 
             ]
         };
            
@@ -2971,10 +2964,18 @@ Roo.extend(Roo.bootstrap.ButtonUploader, Roo.bootstrap.Button,  {
                                 (window.URL && URL.revokeObjectURL && URL) || 
                                 (window.webkitURL && webkitURL);
                         
-         
-         
-         
-        this.selectorEl = this.el.select('.roo-card-upload-selector', true).first();
+        var im = {
+            tag: 'input',
+            type : 'file',
+            cls : 'd-none  roo-card-upload-selector' 
+          
+        };
+        if (this.multiple) {
+            im.multiple = 'multiple';
+        }
+        this.selectorEl = Roo.get(document.body).createChild(im); // so it does not capture click event for navitem.
+       
+        //this.selectorEl = this.el.select('.roo-card-upload-selector', true).first();
         
         this.selectorEl.on('change', this.onFileSelected, this);
          
@@ -3616,7 +3617,7 @@ Roo.bootstrap.menu.Manager = function(){
  * @class Roo.bootstrap.menu.Menu
  * @extends Roo.bootstrap.Component
  * @licence LGPL
- * @children Roo.bootstrap.menu.Item
+ * @children Roo.bootstrap.menu.Item Roo.bootstrap.menu.Separator
  * @parent none
  * Bootstrap Menu class - container for MenuItems - normally has to be added to a object that supports the menu property
  * 
@@ -3624,8 +3625,8 @@ Roo.bootstrap.menu.Manager = function(){
  * @cfg {bool} hidden  if the menu should be hidden when rendered.
  * @cfg {bool} stopEvent (true|false)  Stop event after trigger press (default true)
  * @cfg {bool} isLink (true|false)  the menu has link disable auto expand and collaspe (default false)
-  * @cfg {bool} hideTrigger (true|false)  default false - hide the carret for trigger.
-  * @cfg {String} align  default tl-bl? == below  - how the menu should be aligned. 
+* @cfg {bool} hideTrigger (true|false)  default false - hide the carret for trigger.
+* @cfg {String} align  default tl-bl? == below  - how the menu should be aligned. 
  
  * @constructor
  * Create a new Menu
@@ -4198,9 +4199,9 @@ Roo.extend(Roo.bootstrap.menu.Item, Roo.bootstrap.Component,  {
     },
     onClick : function(e)
     {
-        Roo.log('item on click ');
+        //Roo.log('item on click ');
         
-        if(this.preventDefault){
+        if(this.href === false || this.preventDefault){
             e.preventDefault();
         }
         //this.parent().hideMenuItems();
@@ -4418,6 +4419,8 @@ Roo.extend(Roo.bootstrap.Modal, Roo.bootstrap.Component,  {
             delete this.items;
 
             for(var i =0;i < items.length;i++) {
+                // we force children not to montor widnow resize  - as we do that for them.
+                items[i].monitorWindowResize = false;
                 nitems.push(this.addxtype(Roo.apply({}, items[i])));
             }
         }
@@ -4545,10 +4548,16 @@ Roo.extend(Roo.bootstrap.Modal, Roo.bootstrap.Component,  {
             : this.el.select('.modal-footer div',true).first();
 
     },
+    
+    closeClick : function()
+    {
+        this.hide();
+    },
+    
     initEvents : function()
     {
         if (this.allow_close) {
-            this.closeEl.on('click', this.hide, this);
+            this.closeEl.on('click', this.closeClick, this);
         }
         Roo.EventManager.onWindowResize(this.resize, this, true);
         if (this.editableTitle) {
@@ -4625,6 +4634,14 @@ Roo.extend(Roo.bootstrap.Modal, Roo.bootstrap.Component,  {
         }
         
         this.resizeTo(w,h);
+        // any layout/border etc.. resize..
+        (function () {
+            this.items.forEach( function(e) {
+                e.layout ? e.layout() : false;
+
+            });
+        }).defer(100,this);
+        
     },
 
     show : function() {
@@ -4669,7 +4686,9 @@ Roo.extend(Roo.bootstrap.Modal, Roo.bootstrap.Component,  {
 
         // set zindex here - otherwise it appears to be ignored...
         this.el.setStyle('z-index', Roo.bootstrap.Modal.zIndex++);
-
+        
+        
+        // this is for children that are... layout.Border 
         (function () {
             this.items.forEach( function(e) {
                 e.layout ? e.layout() : false;
@@ -6542,10 +6561,11 @@ Roo.extend(Roo.bootstrap.nav.Item, Roo.bootstrap.Component,  {
         }
         
         if(
-                this.preventDefault || 
-                this.href == '#' 
+                this.preventDefault ||
+                               this.href === false ||
+                this.href === '#' 
         ){
-            Roo.log("NavItem - prevent Default?");
+            //Roo.log("NavItem - prevent Default?");
             e.preventDefault();
         }
         
@@ -8452,7 +8472,7 @@ Roo.grid.ColumnModel = function(config){
 };
 Roo.extend(Roo.grid.ColumnModel, Roo.util.Observable, {
     /**
-     * @cfg {String} header The header text to display in the Grid view.
+     * @cfg {String} header [required] The header text to display in the Grid view.
      */
        /**
      * @cfg {String} xsHeader Header at Bootsrap Extra Small width (default for all)
@@ -8470,66 +8490,66 @@ Roo.extend(Roo.grid.ColumnModel, Roo.util.Observable, {
      * @cfg {String} xlHeader Header at Bootsrap extra Large width
      */
     /**
-     * @cfg {String} dataIndex (Optional) The name of the field in the grid's {@link Roo.data.Store}'s
+     * @cfg {String} dataIndex  The name of the field in the grid's {@link Roo.data.Store}'s
      * {@link Roo.data.Record} definition from which to draw the column's value. If not
      * specified, the column's index is used as an index into the Record's data Array.
      */
     /**
-     * @cfg {Number} width (Optional) The initial width in pixels of the column. Using this
+     * @cfg {Number} width  The initial width in pixels of the column. Using this
      * instead of {@link Roo.grid.Grid#autoSizeColumns} is more efficient.
      */
     /**
-     * @cfg {Boolean} sortable (Optional) True if sorting is to be allowed on this column.
+     * @cfg {Boolean} sortable True if sorting is to be allowed on this column.
      * Defaults to the value of the {@link #defaultSortable} property.
      * Whether local/remote sorting is used is specified in {@link Roo.data.Store#remoteSort}.
      */
     /**
-     * @cfg {Boolean} locked (Optional) True to lock the column in place while scrolling the Grid.  Defaults to false.
+     * @cfg {Boolean} locked  True to lock the column in place while scrolling the Grid.  Defaults to false.
      */
     /**
-     * @cfg {Boolean} fixed (Optional) True if the column width cannot be changed.  Defaults to false.
+     * @cfg {Boolean} fixed  True if the column width cannot be changed.  Defaults to false.
      */
     /**
-     * @cfg {Boolean} resizable (Optional) False to disable column resizing. Defaults to true.
+     * @cfg {Boolean} resizable  False to disable column resizing. Defaults to true.
      */
     /**
-     * @cfg {Boolean} hidden (Optional) True to hide the column. Defaults to false.
+     * @cfg {Boolean} hidden  True to hide the column. Defaults to false.
      */
     /**
-     * @cfg {Function} renderer (Optional) A function used to generate HTML markup for a cell
+     * @cfg {Function} renderer A function used to generate HTML markup for a cell
      * given the cell's data value. See {@link #setRenderer}. If not specified, the
      * default renderer returns the escaped data value. If an object is returned (bootstrap only)
      * then it is treated as a Roo Component object instance, and it is rendered after the initial row is rendered
      */
        /**
-     * @cfg {Roo.grid.GridEditor} editor (Optional) For grid editors - returns the grid editor 
+     * @cfg {Roo.grid.GridEditor} editor  For grid editors - returns the grid editor 
      */
     /**
-     * @cfg {String} align (Optional) Set the CSS text-align property of the column.  Defaults to undefined.
+     * @cfg {String} align (left|right) Set the CSS text-align property of the column.  Defaults to undefined (left).
      */
     /**
-     * @cfg {String} valign (Optional) Set the CSS vertical-align property of the column (eg. middle, top, bottom etc).  Defaults to undefined.
+     * @cfg {String} valign (top|bottom|middle) Set the CSS vertical-align property of the column (eg. middle, top, bottom etc).  Defaults to undefined (middle)
      */
     /**
-     * @cfg {String} cursor (Optional)
+     * @cfg {String} cursor ( auto|default|none|context-menu|help|pointer|progress|wait|cell|crosshair|text|vertical-text|alias|copy|move|no-drop|not-allowed|e-resize|n-resize|ne-resize|nw-resize|s-resize|se-resize|sw-resize|w-resize|ew-resize|ns-resize|nesw-resize|nwse-resize|col-resize|row-resize|all-scroll|zoom-in|zoom-out|grab|grabbing)
      */
     /**
-     * @cfg {String} tooltip (Optional)
+     * @cfg {String} tooltip mouse over tooltip text
      */
     /**
-     * @cfg {Number} xs (Optional) can be '0' for hidden at this size (number less than 12)
+     * @cfg {Number} xs  can be '0' for hidden at this size (number less than 12)
      */
     /**
-     * @cfg {Number} sm (Optional) can be '0' for hidden at this size (number less than 12)
+     * @cfg {Number} sm can be '0' for hidden at this size (number less than 12)
      */
     /**
-     * @cfg {Number} md (Optional) can be '0' for hidden at this size (number less than 12)
+     * @cfg {Number} md can be '0' for hidden at this size (number less than 12)
      */
     /**
-     * @cfg {Number} lg (Optional) can be '0' for hidden at this size (number less than 12)
+     * @cfg {Number} lg   can be '0' for hidden at this size (number less than 12)
      */
        /**
-     * @cfg {Number} xl (Optional) can be '0' for hidden at this size (number less than 12)
+     * @cfg {Number} xl   can be '0' for hidden at this size (number less than 12)
      */
     /**
      * Returns the id of the column at the specified index.
@@ -9136,7 +9156,8 @@ Currently the Table  uses multiple headers to try and handle XL / Medium etc...
  * 
  * @cfg {String} cls table class
  *
- * 
+ *
+ * @cfg {string} empty_results  Text to display for no results 
  * @cfg {boolean} striped Should the rows be alternative striped
  * @cfg {boolean} bordered Add borders to the table
  * @cfg {boolean} hover Add hover highlighting
@@ -9145,14 +9166,18 @@ Currently the Table  uses multiple headers to try and handle XL / Medium etc...
  *                also adds table-responsive (see bootstrap docs for details)
  * @cfg {Boolean} loadMask (true|false) default false
  * @cfg {Boolean} footerShow (true|false) generate tfoot, default true
+ * @cfg {Boolean} footerRow (true|false) generate tfoot with columns of values, default false
  * @cfg {Boolean} headerShow (true|false) generate thead, default true
  * @cfg {Boolean} rowSelection (true|false) default false
  * @cfg {Boolean} cellSelection (true|false) default false
- * @cfg {Boolean} scrollBody (true|false) default false - body scrolled / fixed header
+ * @cfg {Boolean} scrollBody (true|false) default false - body scrolled / fixed header (with resizable columns)
  * @cfg {Roo.bootstrap.PagingToolbar} footer  a paging toolbar
  * @cfg {Boolean} lazyLoad  auto load data while scrolling to the end (default false)
  * @cfg {Boolean} auto_hide_footer  auto hide footer if only one page (default false)
- * @cfg {Boolean} enableColumnResize default true if columns can be resized (drag/drop)
+ * @cfg {Boolean} enableColumnResize default true if columns can be resized = needs scrollBody to be set to work (drag/drop)
+ * @cfg {Boolean} disableAutoSize disable autoSize() and initCSS()
+ *
+ * 
  * @cfg {Number} minColumnWidth default 50 pixels minimum column width 
  * 
  * @constructor
@@ -9314,6 +9339,7 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component,  {
     
     cls: false,
     
+    empty_results : '',
     striped : false,
     scrollBody : false,
     bordered: false,
@@ -9325,8 +9351,10 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component,  {
     store : false,
     loadMask : false,
     footerShow : true,
+    footerRow : false,
     headerShow : true,
     enableColumnResize: true,
+    disableAutoSize: false,
   
     rowSelection : false,
     cellSelection : false,
@@ -9399,9 +9427,10 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component,  {
             
             cfg.cn.push(this.renderBody());
             
-            if(this.footerShow){
+            if(this.footerShow || this.footerRow){
                 cfg.cn.push(this.renderFooter());
             }
+
             // where does this come from?
             //cfg.cls+=  ' TableGrid';
         }
@@ -9488,7 +9517,9 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component,  {
     
     initCSS : function()
     {
-        
+        if(this.disableAutoSize) {
+            return;
+        }
         
         var cm = this.cm, styles = [];
         this.CSS.removeStyleSheet(this.id + '-cssrules');
@@ -9565,7 +9596,9 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component,  {
                 }
                 
                 styles.push('#' , this.id , ' .x-hcol-' , i, " { ", width, hidden," }\n",
-                            '#' , this.id , ' .x-grid-split-' , i, " { ", left, splithide,'height:', (headHeight - 4), "px;}\n"
+                            '#' , this.id , ' .x-grid-split-' , i, " { ", left, splithide, 'height:', (headHeight - 4), "px;}\n",
+                            // this is the popover version..
+                            '.popover-inner #' , this.id , ' .x-grid-split-' , i, " { ", left, splithide, 'height:', 100, "%;}\n"
                 );
             }
             
@@ -9958,8 +9991,6 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component,  {
         return footer;
     },
     
-    
-    
     onLoad : function()
     {
 //        Roo.log('ds onload');
@@ -10000,11 +10031,13 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component,  {
                 }
                 
             }, this);
+        } else if (this.empty_results.length) {
+            this.el.mask(this.empty_results, 'no-spinner');
         }
         
         var tfoot = this.el.select('tfoot', true).first();
         
-        if(this.footerShow && this.auto_hide_footer && this.mainFoot){
+        if(this.footerShow && !this.footerRow && this.auto_hide_footer && this.mainFoot){
             
             this.mainFoot.setVisibilityMode(Roo.Element.DISPLAY).hide();
             
@@ -10014,6 +10047,30 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component,  {
                 this.mainFoot.show();
             }
         }
+
+        if(!this.footerShow && this.footerRow) {
+
+            var tr = {
+                tag : 'tr',
+                cn : []
+            };
+
+            for(var i = 0, len = cm.getColumnCount(); i < len; i++){
+                var footer = typeof(cm.config[i].footer) == "function" ? cm.config[i].footer(ds, cm.config[i]) : cm.config[i].footer;
+                var td = {
+                    tag: 'td',
+                    cls : ' x-fcol-' + i,
+                    html: footer
+                };
+
+                tr.cn.push(td);
+                
+            }
+            
+            tfoot.dom.innerHTML = '';
+
+            tfoot.createChild(tr);
+        }
         
         Roo.each(this.el.select('tbody td', true).elements, function(e){
             e.on('mouseover', _this.onMouseover, _this);
@@ -10281,7 +10338,7 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component,  {
             var id = false;
             
             if(typeof(renderer) !== 'undefined'){
-                value = renderer(d.data[cm.getDataIndex(i)], false, d);
+                value = renderer.call(config, d.data[cm.getDataIndex(i)], false, d);
             }
             // if object are returned, then they are expected to be Roo.bootstrap.Component instances
             // and are rendered into the cells after the row is rendered - using the id for the element.
@@ -10378,7 +10435,7 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component,  {
     
     onBeforeLoad : function()
     {
-        
+        this.el.unmask(); // if needed.
     },
      /**
      * Remove all rows
@@ -10488,6 +10545,9 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component,  {
      */
     autoSize : function()
     {
+        if(this.disableAutoSize) {
+            return;
+        }
         //var ctr = Roo.get(this.container.dom.parentElement);
         var ctr = Roo.get(this.el.dom);
         
@@ -11258,7 +11318,13 @@ Roo.extend(Roo.form.Action.Submit, Roo.form.Action, {
         }
         var ret = false;
         try {
-            ret = Roo.decode(response.responseText);
+            var rt = response.responseText;
+            if (rt.match(/^\<!--\[CDATA\[/)) {
+                rt = rt.replace(/^\<!--\[CDATA\[/,'');
+                rt = rt.replace(/\]\]--\>$/,'');
+            }
+            
+            ret = Roo.decode(rt);
         } catch (e) {
             ret = {
                 success: false,
@@ -12142,68 +12208,68 @@ Roo.form.VTypes = function(){
          * The function used to validate email addresses
          * @param {String} value The email address
          */
-        'email' : function(v){
+        email : function(v){
             return email.test(v);
         },
         /**
          * The error text to display when the email validation function returns false
          * @type String
          */
-        'emailText' : 'This field should be an e-mail address in the format "user@domain.com"',
+        emailText : 'This field should be an e-mail address in the format "user@domain.com"',
         /**
          * The keystroke filter mask to be applied on email input
          * @type RegExp
          */
-        'emailMask' : /[a-z0-9_\.\-@]/i,
+        emailMask : /[a-z0-9_\.\-@]/i,
 
         /**
          * The function used to validate URLs
          * @param {String} value The URL
          */
-        'url' : function(v){
+        url : function(v){
             return url.test(v);
         },
         /**
          * The error text to display when the url validation function returns false
          * @type String
          */
-        'urlText' : 'This field should be a URL in the format "http:/'+'/www.domain.com"',
+        urlText : 'This field should be a URL in the format "http:/'+'/www.domain.com"',
         
         /**
          * The function used to validate alpha values
          * @param {String} value The value
          */
-        'alpha' : function(v){
+        alpha : function(v){
             return alpha.test(v);
         },
         /**
          * The error text to display when the alpha validation function returns false
          * @type String
          */
-        'alphaText' : 'This field should only contain letters and _',
+        alphaText : 'This field should only contain letters and _',
         /**
          * The keystroke filter mask to be applied on alpha input
          * @type RegExp
          */
-        'alphaMask' : /[a-z_]/i,
+        alphaMask : /[a-z_]/i,
 
         /**
          * The function used to validate alphanumeric values
          * @param {String} value The value
          */
-        'alphanum' : function(v){
+        alphanum : function(v){
             return alphanum.test(v);
         },
         /**
          * The error text to display when the alphanumeric validation function returns false
          * @type String
          */
-        'alphanumText' : 'This field should only contain letters, numbers and _',
+        alphanumText : 'This field should only contain letters, numbers and _',
         /**
          * The keystroke filter mask to be applied on alphanumeric input
          * @type RegExp
          */
-        'alphanumMask' : /[a-z0-9_]/i
+        alphanumMask : /[a-z0-9_]/i
     };
 }();/*
  * - LGPL
@@ -12480,7 +12546,6 @@ Roo.extend(Roo.bootstrap.form.Input, Roo.bootstrap.Component,  {
     
     getAutoCreate : function()
     {
-        var align = (!this.labelAlign) ? this.parentLabelAlign() : this.labelAlign;
         
         var id = Roo.id();
         
@@ -12609,6 +12674,34 @@ Roo.extend(Roo.bootstrap.form.Input, Roo.bootstrap.Component,  {
                 inputblock.cn.push(feedback);
             }
         };
+        
+        
+        
+        cfg = this.getAutoCreateLabel( cfg, inputblock );
+        
+       
+         
+        
+        if (this.parentType === 'Navbar' &&  this.parent().bar) {
+           cfg.cls += ' navbar-form';
+        }
+        
+        if (this.parentType === 'NavGroup' && !(Roo.bootstrap.version == 4 && this.parent().form)) {
+            // on BS4 we do this only if not form 
+            cfg.cls += ' navbar-form';
+            cfg.tag = 'li';
+        }
+        
+        return cfg;
+        
+    },
+    /**
+     * autocreate the label - also used by textara... ?? and others?
+     */
+    getAutoCreateLabel : function( cfg, inputblock )
+    {
+        var align = (!this.labelAlign) ? this.parentLabelAlign() : this.labelAlign;
+       
         var indicator = {
             tag : 'i',
             cls : 'roo-required-indicator ' + (this.indicatorpos == 'right'  ? 'right' : 'left') +'-indicator text-danger fa fa-lg fa-star',
@@ -12752,20 +12845,10 @@ Roo.extend(Roo.bootstrap.form.Input, Roo.bootstrap.Component,  {
                 
                 
         };
-        
-        if (this.parentType === 'Navbar' &&  this.parent().bar) {
-           cfg.cls += ' navbar-form';
-        }
-        
-        if (this.parentType === 'NavGroup' && !(Roo.bootstrap.version == 4 && this.parent().form)) {
-            // on BS4 we do this only if not form 
-            cfg.cls += ' navbar-form';
-            cfg.tag = 'li';
-        }
-        
         return cfg;
-        
     },
+    
+    
     /**
      * return the real input element.
      */
@@ -12914,12 +12997,12 @@ Roo.extend(Roo.bootstrap.form.Input, Roo.bootstrap.Component,  {
         }
         if(typeof this.validator == "function"){
             var msg = this.validator(value);
-            if(msg !== true){
-                return false;
-            }
             if (typeof(msg) == 'string') {
                 this.invalidText = msg;
             }
+            if(msg !== true){
+                return false;
+            }
         }
         
         if(this.regex && !this.regex.test(value)){
@@ -13454,74 +13537,10 @@ Roo.extend(Roo.bootstrap.form.TextArea, Roo.bootstrap.form.Input,  {
             
         }
         
-        if (align ==='left' && this.fieldLabel.length) {
-            cfg.cn = [
-                {
-                    tag: 'label',
-                    'for' :  id,
-                    cls : 'control-label',
-                    html : this.fieldLabel
-                },
-                {
-                    cls : "",
-                    cn: [
-                        inputblock
-                    ]
-                }
-
-            ];
-            
-            if(this.labelWidth > 12){
-                cfg.cn[0].style = "width: " + this.labelWidth + 'px';
-            }
-
-            if(this.labelWidth < 13 && this.labelmd == 0){
-                this.labelmd = this.labelWidth;
-            }
-
-            if(this.labellg > 0){
-                cfg.cn[0].cls += ' col-lg-' + this.labellg;
-                cfg.cn[1].cls += ' col-lg-' + (12 - this.labellg);
-            }
-
-            if(this.labelmd > 0){
-                cfg.cn[0].cls += ' col-md-' + this.labelmd;
-                cfg.cn[1].cls += ' col-md-' + (12 - this.labelmd);
-            }
-
-            if(this.labelsm > 0){
-                cfg.cn[0].cls += ' col-sm-' + this.labelsm;
-                cfg.cn[1].cls += ' col-sm-' + (12 - this.labelsm);
-            }
-
-            if(this.labelxs > 0){
-                cfg.cn[0].cls += ' col-xs-' + this.labelxs;
-                cfg.cn[1].cls += ' col-xs-' + (12 - this.labelxs);
-            }
-            
-        } else if ( this.fieldLabel.length) {
-            cfg.cn = [
-
-               {
-                   tag: 'label',
-                   //cls : 'input-group-addon',
-                   html : this.fieldLabel
-
-               },
-
-               inputblock
-
-           ];
-
-        } else {
-
-            cfg.cn = [
-
-                inputblock
+        
+        cfg = this.getAutoCreateLabel( cfg, inputblock );
 
-            ];
-                
-        }
+         
         
         if (this.disabled) {
             input.disabled=true;
@@ -13549,11 +13568,11 @@ Roo.extend(Roo.bootstrap.form.TextArea, Roo.bootstrap.form.Input,  {
         }
         
         var label = this.el.select('label', true).first();
-        var icon = this.el.select('i.fa-star', true).first();
+        //var icon = this.el.select('i.fa-star', true).first();
         
-        if(label && icon){
-            icon.remove();
-        }
+        //if(label && icon){
+        //    icon.remove();
+        //}
         this.el.removeClass( this.validClass);
         this.inputEl().removeClass('is-invalid');
          
@@ -13595,9 +13614,9 @@ Roo.extend(Roo.bootstrap.form.TextArea, Roo.bootstrap.form.Input,  {
         var label = this.el.select('label', true).first();
         var icon = this.el.select('i.fa-star', true).first();
         
-        if(label && icon){
-            icon.remove();
-        }
+        //if(label && icon){
+        //    icon.remove();
+        //}
         if (Roo.bootstrap.version == 3) {
             this.el.addClass(this.validClass);
         } else {
@@ -13643,16 +13662,17 @@ Roo.extend(Roo.bootstrap.form.TextArea, Roo.bootstrap.form.Input,  {
         }
         
         var label = this.el.select('label', true).first();
-        var icon = this.el.select('i.fa-star', true).first();
+        //var icon = this.el.select('i.fa-star', true).first();
         
-        if(!this.getValue().length && label && !icon){
-            this.el.createChild({
+        //if(!this.getValue().length && label && !icon){
+          /*  this.el.createChild({
                 tag : 'i',
                 cls : 'text-danger fa fa-lg fa-star',
                 tooltip : 'This field is required',
                 style : 'margin-right:5px;'
             }, label, true);
-        }
+            */
+        //}
         
         if (Roo.bootstrap.version == 3) {
             this.el.addClass(this.invalidClass);
@@ -13937,12 +13957,14 @@ Roo.extend(Roo.bootstrap.form.TriggerField, Roo.bootstrap.form.Input,  {
             cls : 'roo-required-indicator ' + (this.indicatorpos == 'right'  ? 'right' : 'left') +'-indicator text-danger fa fa-lg fa-star',
             tooltip : 'This field is required'
         };
-        if (Roo.bootstrap.version == 4) {
+      
+        if (this.allowBlank) {
             indicator = {
                 tag : 'i',
                 style : 'display:none'
             };
         }
+         
         
         
         if (align ==='left' && this.fieldLabel.length) {
@@ -15154,8 +15176,8 @@ Roo.data.Store = function(config){
          * If you return Json { data: [] , success: false, .... } then this will be thrown with the following args
          * 
          * @param {Proxy} 
-         * @param {Object} return from JsonData.reader() - success, totalRecords, records
-         * @param {Object} load options 
+         * @param {Object} ret return data from JsonData.reader() - success, totalRecords, records
+         * @param {Object} opts - load Options
          * @param {Object} jsonData from your request (normally this contains the Exception)
          */
         loadexception : true
@@ -15339,6 +15361,16 @@ Roo.extend(Roo.data.Store, Roo.util.Observable, {
      * <p>
      * @param {Object} options An object containing properties which control loading options:<ul>
      * <li>params {Object} An object containing properties to pass as HTTP parameters to a remote data source.</li>
+     * <li>params.data {Object} if you are using a MemoryProxy / JsonReader, use this as the data to load stuff..
+     * <pre>
+                {
+                    data : data,  // array of key=>value data like JsonReader
+                    total : data.length,
+                    success : true
+                    
+                }
+        </pre>
+            }.</li>
      * <li>callback {Function} A function to be called after the Records have been loaded. The callback is
      * passed the following arguments:<ul>
      * <li>r : Roo.data.Record[]</li>
@@ -16057,14 +16089,16 @@ Roo.extend(Roo.data.DataProxy, Roo.util.Observable);
  */
 /**
  * @class Roo.data.MemoryProxy
+ * @extends Roo.data.DataProxy
  * An implementation of Roo.data.DataProxy that simply passes the data specified in its constructor
  * to the Reader when its load method is called.
  * @constructor
- * @param {Object} data The data object which the Reader uses to construct a block of Roo.data.Records.
+ * @param {Object} config  A config object containing the objects needed for the Store to access data,
  */
-Roo.data.MemoryProxy = function(data){
-    if (data.data) {
-        data = data.data;
+Roo.data.MemoryProxy = function(config){
+    var data = config;
+    if (typeof(config) != 'undefined' && typeof(config.data) != 'undefined') {
+        data = config.data;
     }
     Roo.data.MemoryProxy.superclass.constructor.call(this);
     this.data = data;
@@ -16072,6 +16106,9 @@ Roo.data.MemoryProxy = function(data){
 
 Roo.extend(Roo.data.MemoryProxy, Roo.data.DataProxy, {
     
+    /**
+     *  @cfg {Object} data The data object which the Reader uses to construct a block of Roo.data.Records.
+     */
     /**
      * Load data from the requested source (in this case an in-memory
      * data object passed to the constructor), read the data object into
@@ -16146,24 +16183,24 @@ Roo.extend(Roo.data.HttpProxy, Roo.data.DataProxy, {
     // thse are take from connection...
     
     /**
-     * @cfg {String} url (Optional) The default URL to be used for requests to the server. (defaults to undefined)
+     * @cfg {String} url  The default URL to be used for requests to the server. (defaults to undefined)
      */
     /**
-     * @cfg {Object} extraParams (Optional) An object containing properties which are used as
+     * @cfg {Object} extraParams  An object containing properties which are used as
      * extra parameters to each request made by this object. (defaults to undefined)
      */
     /**
-     * @cfg {Object} defaultHeaders (Optional) An object containing request headers which are added
+     * @cfg {Object} defaultHeaders   An object containing request headers which are added
      *  to each request made by this object. (defaults to undefined)
      */
     /**
-     * @cfg {String} method (Optional) The default HTTP method to be used for requests. (defaults to undefined; if not set but parms are present will use POST, otherwise GET)
+     * @cfg {String} method (GET|POST)  The default HTTP method to be used for requests. (defaults to undefined; if not set but parms are present will use POST, otherwise GET)
      */
     /**
-     * @cfg {Number} timeout (Optional) The timeout in milliseconds to be used for requests. (defaults to 30000)
+     * @cfg {Number} timeout The timeout in milliseconds to be used for requests. (defaults to 30000)
      */
      /**
-     * @cfg {Boolean} autoAbort (Optional) Whether this request should abort any pending requests. (defaults to false)
+     * @cfg {Boolean} autoAbort Whether this request should abort any pending requests. (defaults to false)
      * @type Boolean
      */
   
@@ -16237,14 +16274,10 @@ Roo.extend(Roo.data.HttpProxy, Roo.data.DataProxy, {
         try {
             result = o.reader.read(response);
         }catch(e){
+            o.success = false;
+            o.raw = { errorMsg : response.responseText };
             this.fireEvent("loadexception", this, o, response, e);
-            o.request.callback.call(o.request.scope, {
-                    success : false,
-                    raw : {
-                        errorMsg : response
-                    }
-                    
-                }, o.request.arg, false);
+            o.request.callback.call(o.request.scope, o, o.request.arg, false);
             return;
         }
         
@@ -16650,19 +16683,27 @@ Roo.extend(Roo.data.JsonReader, Roo.data.DataReader, {
         }
         var records = [];
         for(var i = 0; i < c; i++){
-                var n = root[i];
+            var n = root[i];
             var values = {};
             var id = this.getId(n);
             for(var j = 0; j < fl; j++){
                 f = fi[j];
-            var v = this.ef[j](n);
-            if (!f.convert) {
-                Roo.log('missing convert for ' + f.name);
-                Roo.log(f);
-                continue;
-            }
-            values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue);
-            }
+                               var v = this.ef[j](n);
+                               if (!f.convert) {
+                                       Roo.log('missing convert for ' + f.name);
+                                       Roo.log(f);
+                                       continue;
+                               }
+                               values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue);
+            }
+                       if (!Record) {
+                               return {
+                                       raw : { errorMsg : "JSON Reader Error: fields or metadata not available to create Record" },
+                                       success : false,
+                                       records : [],
+                                       totalRecords : 0
+                               };
+                       }
             var record = new Record(values, id);
             record.json = n;
             records[i] = record;
@@ -17282,12 +17323,13 @@ Roo.extend(Roo.bootstrap.form.ComboBox, Roo.bootstrap.form.TriggerField, {
             cls : 'roo-required-indicator ' + (this.indicatorpos == 'right'  ? 'right' : 'left') +'-indicator text-danger fa fa-lg fa-star',
             tooltip : 'This field is required'
         };
-        if (Roo.bootstrap.version == 4) {
+         
+        if (this.allowBlank) {
             indicator = {
                 tag : 'i',
                 style : 'display:none'
             };
-        }
+        } 
         if (align ==='left' && this.fieldLabel.length) {
             
             cfg.cls += ' roo-form-group-label-left'  + (Roo.bootstrap.version == 4 ? ' row' : '');
@@ -23897,6 +23939,7 @@ Roo.apply(Roo.bootstrap.form.DateField,  {
  * @class Roo.bootstrap.form.TimeField
  * @extends Roo.bootstrap.form.Input
  * Bootstrap DateField class
+ * @cfg {Number} minuteStep the minutes is always the multiple of a fixed number, default 1
  * 
  * 
  * @constructor
@@ -23939,6 +23982,7 @@ Roo.extend(Roo.bootstrap.form.TimeField, Roo.bootstrap.form.Input,  {
      * valid according to {@link Date#parseDate} (defaults to 'H:i').
      */
     format : "H:i",
+    minuteStep : 1,
 
     getAutoCreate : function()
     {
@@ -24319,14 +24363,16 @@ Roo.extend(Roo.bootstrap.form.TimeField, Roo.bootstrap.form.Input,  {
     onIncrementMinutes: function()
     {
         Roo.log('onIncrementMinutes');
-        this.time = this.time.add(Date.MINUTE, 1);
+        var minutesToAdd = Math.round((parseInt(this.time.format('i')) + this.minuteStep) / this.minuteStep) * this.minuteStep - parseInt(this.time.format('i'));
+        this.time = this.time.add(Date.MINUTE, minutesToAdd);
         this.update();
     },
     
     onDecrementMinutes: function()
     {
         Roo.log('onDecrementMinutes');
-        this.time = this.time.add(Date.MINUTE, -1);
+        var minutesToSubtract = parseInt(this.time.format('i')) - Math.round((parseInt(this.time.format('i')) - this.minuteStep) / this.minuteStep) * this.minuteStep;
+        this.time = this.time.add(Date.MINUTE, -1 * minutesToSubtract);
         this.update();
     },
     
@@ -25954,265 +26000,5061 @@ Roo.extend(Roo.bootstrap.form.SecurePass, Roo.bootstrap.form.Input, {
         return this.IsLongEnough(pwd, 6) || !this.IsLongEnough(pwd, 0);
     }
           
-})//<script type="text/javascript">
-
-/*
- * Based  Ext JS Library 1.1.1
- * Copyright(c) 2006-2007, Ext JS, LLC.
- * LGPL
- *
- */
-/**
- * @class Roo.HtmlEditorCore
- * @extends Roo.Component
- * Provides a the editing component for the HTML editors in Roo. (bootstrap and Roo.form)
- *
- * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
- */
+});Roo.rtf = {}; // namespace
+Roo.rtf.Hex = function(hex)
+{
+    this.hexstr = hex;
+};
+Roo.rtf.Paragraph = function(opts)
+{
+    this.content = []; ///??? is that used?
+};Roo.rtf.Span = function(opts)
+{
+    this.value = opts.value;
+};
 
-Roo.HtmlEditorCore = function(config){
-    
-    
-    Roo.HtmlEditorCore.superclass.constructor.call(this, config);
-    
+Roo.rtf.Group = function(parent)
+{
+    // we dont want to acutally store parent - it will make debug a nightmare..
+    this.content = [];
+    this.cn  = [];
+     
+       
     
-    this.addEvents({
-        /**
-         * @event initialize
-         * Fires when the editor is fully initialized (including the iframe)
-         * @param {Roo.HtmlEditorCore} this
-         */
-        initialize: true,
-        /**
-         * @event activate
-         * Fires when the editor is first receives the focus. Any insertion must wait
-         * until after this event.
-         * @param {Roo.HtmlEditorCore} this
-         */
-        activate: true,
-         /**
-         * @event beforesync
-         * Fires before the textarea is updated with content from the editor iframe. Return false
-         * to cancel the sync.
-         * @param {Roo.HtmlEditorCore} this
-         * @param {String} html
-         */
-        beforesync: true,
-         /**
-         * @event beforepush
-         * Fires before the iframe editor is updated with content from the textarea. Return false
-         * to cancel the push.
-         * @param {Roo.HtmlEditorCore} this
-         * @param {String} html
-         */
-        beforepush: true,
-         /**
-         * @event sync
-         * Fires when the textarea is updated with content from the editor iframe.
-         * @param {Roo.HtmlEditorCore} this
-         * @param {String} html
-         */
-        sync: true,
-         /**
-         * @event push
-         * Fires when the iframe editor is updated with content from the textarea.
-         * @param {Roo.HtmlEditorCore} this
-         * @param {String} html
-         */
-        push: true,
+};
+
+Roo.rtf.Group.prototype = {
+    ignorable : false,
+    content: false,
+    cn: false,
+    addContent : function(node) {
+        // could set styles...
+        this.content.push(node);
+    },
+    addChild : function(cn)
+    {
+        this.cn.push(cn);
+    },
+    // only for images really...
+    toDataURL : function()
+    {
+        var mimetype = false;
+        switch(true) {
+            case this.content.filter(function(a) { return a.value == 'pngblip' } ).length > 0: 
+                mimetype = "image/png";
+                break;
+             case this.content.filter(function(a) { return a.value == 'jpegblip' } ).length > 0:
+                mimetype = "image/jpeg";
+                break;
+            default :
+                return 'about:blank'; // ?? error?
+        }
         
-        /**
-         * @event editorevent
-         * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
-         * @param {Roo.HtmlEditorCore} this
-         */
-        editorevent: true
         
-    });
-    
-    // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
+        var hexstring = this.content[this.content.length-1].value;
+        
+        return 'data:' + mimetype + ';base64,' + btoa(hexstring.match(/\w{2}/g).map(function(a) {
+            return String.fromCharCode(parseInt(a, 16));
+        }).join(""));
+    }
     
-    // defaults : white / black...
-    this.applyBlacklists();
+};
+// this looks like it's normally the {rtf{ .... }}
+Roo.rtf.Document = function()
+{
+    // we dont want to acutally store parent - it will make debug a nightmare..
+    this.rtlch  = [];
+    this.content = [];
+    this.cn = [];
     
+};
+Roo.extend(Roo.rtf.Document, Roo.rtf.Group, { 
+    addChild : function(cn)
+    {
+        this.cn.push(cn);
+        switch(cn.type) {
+            case 'rtlch': // most content seems to be inside this??
+            case 'listtext':
+            case 'shpinst':
+                this.rtlch.push(cn);
+                return;
+            default:
+                this[cn.type] = cn;
+        }
+        
+    },
     
+    getElementsByType : function(type)
+    {
+        var ret =  [];
+        this._getElementsByType(type, ret, this.cn, 'rtf');
+        return ret;
+    },
+    _getElementsByType : function (type, ret, search_array, path)
+    {
+        search_array.forEach(function(n,i) {
+            if (n.type == type) {
+                n.path = path + '/' + n.type + ':' + i;
+                ret.push(n);
+            }
+            if (n.cn.length > 0) {
+                this._getElementsByType(type, ret, n.cn, path + '/' + n.type+':'+i);
+            }
+        },this);
+    }
     
+});
+Roo.rtf.Ctrl = function(opts)
+{
+    this.value = opts.value;
+    this.param = opts.param;
 };
+/**
+ *
+ *
+ * based on this https://github.com/iarna/rtf-parser
+ * it's really only designed to extract pict from pasted RTF 
+ *
+ * usage:
+ *
+ *  var images = new Roo.rtf.Parser().parse(a_string).filter(function(g) { return g.type == 'pict'; });
+ *  
+ *
+ */
 
 
-Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
 
 
-     /**
-     * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field 
-     */
+Roo.rtf.Parser = function(text) {
+    //super({objectMode: true})
+    this.text = '';
+    this.parserState = this.parseText;
     
-    owner : false,
+    // these are for interpeter...
+    this.doc = {};
+    ///this.parserState = this.parseTop
+    this.groupStack = [];
+    this.hexStore = [];
+    this.doc = false;
     
-     /**
-     * @cfg {String} resizable  's' or 'se' or 'e' - wrapps the element in a
-     *                        Roo.resizable.
-     */
-    resizable : false,
-     /**
-     * @cfg {Number} height (in pixels)
-     */   
-    height: 300,
-   /**
-     * @cfg {Number} width (in pixels)
-     */   
-    width: 500,
+    this.groups = []; // where we put the return.
     
-    /**
-     * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
-     * 
-     */
-    stylesheets: false,
+    for (var ii = 0; ii < text.length; ++ii) {
+        ++this.cpos;
+        
+        if (text[ii] === '\n') {
+            ++this.row;
+            this.col = 1;
+        } else {
+            ++this.col;
+        }
+        this.parserState(text[ii]);
+    }
     
-    /**
-     * @cfg {boolean} allowComments - default false - allow comments in HTML source - by default they are stripped - if you are editing email you may need this.
-     */
-    allowComments: false,
-    // id of frame..
-    frameId: false,
     
-    // private properties
-    validationEvent : false,
-    deferHeight: true,
-    initialized : false,
-    activated : false,
-    sourceEditMode : false,
-    onFocus : Roo.emptyFn,
-    iframePad:3,
-    hideMode:'offsets',
     
-    clearUp: true,
+};
+Roo.rtf.Parser.prototype = {
+    text : '', // string being parsed..
+    controlWord : '',
+    controlWordParam :  '',
+    hexChar : '',
+    doc : false,
+    group: false,
+    groupStack : false,
+    hexStore : false,
     
-    // blacklist + whitelisted elements..
-    black: false,
-    white: false,
-     
-    bodyCls : '',
+    
+    cpos : 0, 
+    row : 1, // reportin?
+    col : 1, //
 
-    /**
-     * Protected method that will not generally be called directly. It
-     * is called when the editor initializes the iframe with HTML contents. Override this method if you
-     * want to change the initialization markup of the iframe (e.g. to add stylesheets).
-     */
-    getDocMarkup : function(){
-        // body styles..
-        var st = '';
-        
-        // inherit styels from page...?? 
-        if (this.stylesheets === false) {
-            
-            Roo.get(document.head).select('style').each(function(node) {
-                st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
-            });
-            
-            Roo.get(document.head).select('link').each(function(node) { 
-                st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
-            });
-            
-        } else if (!this.stylesheets.length) {
-                // simple..
-                st = '<style type="text/css">' +
-                    'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
-                   '</style>';
-        } else {
-            for (var i in this.stylesheets) { 
-                st += '<link rel="stylesheet" href="' + this.stylesheets[i] +'" type="text/css">';
-            }
-            
+     
+    push : function (el)
+    {
+        var m = 'cmd'+ el.type;
+        if (typeof(this[m]) == 'undefined') {
+            Roo.log('invalid cmd:' + el.type);
+            return;
+        }
+        this[m](el);
+        //Roo.log(el);
+    },
+    flushHexStore : function()
+    {
+        if (this.hexStore.length < 1) {
+            return;
         }
+        var hexstr = this.hexStore.map(
+            function(cmd) {
+                return cmd.value;
+        }).join('');
         
-        st +=  '<style type="text/css">' +
-            'IMG { cursor: pointer } ' +
-        '</style>';
-
-        var cls = 'roo-htmleditor-body';
+        this.group.addContent( new Roo.rtf.Hex( hexstr ));
+              
+            
+        this.hexStore.splice(0)
         
-        if(this.bodyCls.length){
-            cls += ' ' + this.bodyCls;
+    },
+    
+    cmdgroupstart : function()
+    {
+        this.flushHexStore();
+        if (this.group) {
+            this.groupStack.push(this.group);
         }
-        
-        return '<html><head>' + st  +
-            //<style type="text/css">' +
-            //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
-            //'</style>' +
-            ' </head><body contenteditable="true" data-enable-grammerly="true" class="' +  cls + '"></body></html>';
+         // parent..
+        if (this.doc === false) {
+            this.group = this.doc = new Roo.rtf.Document();
+            return;
+            
+        }
+        this.group = new Roo.rtf.Group(this.group);
     },
-
-    // private
-    onRender : function(ct, position)
+    cmdignorable : function()
     {
-        var _t = this;
-        //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
-        this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
-        
-        
-        this.el.dom.style.border = '0 none';
-        this.el.dom.setAttribute('tabIndex', -1);
-        this.el.addClass('x-hidden hide');
-        
+        this.flushHexStore();
+        this.group.ignorable = true;
+    },
+    cmdendparagraph : function()
+    {
+        this.flushHexStore();
+        this.group.addContent(new Roo.rtf.Paragraph());
+    },
+    cmdgroupend : function ()
+    {
+        this.flushHexStore();
+        var endingGroup = this.group;
         
         
-        if(Roo.isIE){ // fix IE 1px bogus margin
-            this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
+        this.group = this.groupStack.pop();
+        if (this.group) {
+            this.group.addChild(endingGroup);
         }
-       
-        
-        this.frameId = Roo.id();
-        
-         
-        
-        var iframe = this.owner.wrap.createChild({
-            tag: 'iframe',
-            cls: 'form-control', // bootstrap..
-            id: this.frameId,
-            name: this.frameId,
-            frameBorder : 'no',
-            'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL  :  "javascript:false"
-        }, this.el
-        );
         
         
-        this.iframe = iframe.dom;
-
-         this.assignDocWin();
-        
-        this.doc.designMode = 'on';
-       
-        this.doc.open();
-        this.doc.write(this.getDocMarkup());
-        this.doc.close();
-
         
-        var task = { // must defer to wait for browser to be ready
-            run : function(){
-                //console.log("run task?" + this.doc.readyState);
-                this.assignDocWin();
-                if(this.doc.body || this.doc.readyState == 'complete'){
-                    try {
-                        this.doc.designMode="on";
-                    } catch (e) {
-                        return;
-                    }
-                    Roo.TaskMgr.stop(task);
-                    this.initEditor.defer(10, this);
-                }
-            },
-            interval : 10,
-            duration: 10000,
-            scope: this
-        };
-        Roo.TaskMgr.start(task);
-
+        var doc = this.group || this.doc;
+        //if (endingGroup instanceof FontTable) {
+        //  doc.fonts = endingGroup.table
+        //} else if (endingGroup instanceof ColorTable) {
+        //  doc.colors = endingGroup.table
+        //} else if (endingGroup !== this.doc && !endingGroup.get('ignorable')) {
+        if (endingGroup.ignorable === false) {
+            //code
+            this.groups.push(endingGroup);
+           // Roo.log( endingGroup );
+        }
+            //Roo.each(endingGroup.content, function(item)) {
+            //    doc.addContent(item);
+            //}
+            //process.emit('debug', 'GROUP END', endingGroup.type, endingGroup.get('ignorable'))
+        //}
     },
-
-    // private
-    onResize : function(w, h)
+    cmdtext : function (cmd)
+    {
+        this.flushHexStore();
+        if (!this.group) { // an RTF fragment, missing the {\rtf1 header
+            //this.group = this.doc
+            return;  // we really don't care about stray text...
+        }
+        this.group.addContent(new Roo.rtf.Span(cmd));
+    },
+    cmdcontrolword : function (cmd)
+    {
+        this.flushHexStore();
+        if (!this.group.type) {
+            this.group.type = cmd.value;
+            return;
+        }
+        this.group.addContent(new Roo.rtf.Ctrl(cmd));
+        // we actually don't care about ctrl words...
+        return ;
+        /*
+        var method = 'ctrl$' + cmd.value.replace(/-(.)/g, (_, char) => char.toUpperCase())
+        if (this[method]) {
+            this[method](cmd.param)
+        } else {
+            if (!this.group.get('ignorable')) process.emit('debug', method, cmd.param)
+        }
+        */
+    },
+    cmdhexchar : function(cmd) {
+        this.hexStore.push(cmd);
+    },
+    cmderror : function(cmd) {
+        throw cmd.value;
+    },
+    
+    /*
+      _flush (done) {
+        if (this.text !== '\u0000') this.emitText()
+        done()
+      }
+      */
+      
+      
+    parseText : function(c)
+    {
+        if (c === '\\') {
+            this.parserState = this.parseEscapes;
+        } else if (c === '{') {
+            this.emitStartGroup();
+        } else if (c === '}') {
+            this.emitEndGroup();
+        } else if (c === '\x0A' || c === '\x0D') {
+            // cr/lf are noise chars
+        } else {
+            this.text += c;
+        }
+    },
+    
+    parseEscapes: function (c)
+    {
+        if (c === '\\' || c === '{' || c === '}') {
+            this.text += c;
+            this.parserState = this.parseText;
+        } else {
+            this.parserState = this.parseControlSymbol;
+            this.parseControlSymbol(c);
+        }
+    },
+    parseControlSymbol: function(c)
+    {
+        if (c === '~') {
+            this.text += '\u00a0'; // nbsp
+            this.parserState = this.parseText
+        } else if (c === '-') {
+             this.text += '\u00ad'; // soft hyphen
+        } else if (c === '_') {
+            this.text += '\u2011'; // non-breaking hyphen
+        } else if (c === '*') {
+            this.emitIgnorable();
+            this.parserState = this.parseText;
+        } else if (c === "'") {
+            this.parserState = this.parseHexChar;
+        } else if (c === '|') { // formula cacter
+            this.emitFormula();
+            this.parserState = this.parseText;
+        } else if (c === ':') { // subentry in an index entry
+            this.emitIndexSubEntry();
+            this.parserState = this.parseText;
+        } else if (c === '\x0a') {
+            this.emitEndParagraph();
+            this.parserState = this.parseText;
+        } else if (c === '\x0d') {
+            this.emitEndParagraph();
+            this.parserState = this.parseText;
+        } else {
+            this.parserState = this.parseControlWord;
+            this.parseControlWord(c);
+        }
+    },
+    parseHexChar: function (c)
+    {
+        if (/^[A-Fa-f0-9]$/.test(c)) {
+            this.hexChar += c;
+            if (this.hexChar.length >= 2) {
+              this.emitHexChar();
+              this.parserState = this.parseText;
+            }
+            return;
+        }
+        this.emitError("Invalid character \"" + c + "\" in hex literal.");
+        this.parserState = this.parseText;
+        
+    },
+    parseControlWord : function(c)
+    {
+        if (c === ' ') {
+            this.emitControlWord();
+            this.parserState = this.parseText;
+        } else if (/^[-\d]$/.test(c)) {
+            this.parserState = this.parseControlWordParam;
+            this.controlWordParam += c;
+        } else if (/^[A-Za-z]$/.test(c)) {
+          this.controlWord += c;
+        } else {
+          this.emitControlWord();
+          this.parserState = this.parseText;
+          this.parseText(c);
+        }
+    },
+    parseControlWordParam : function (c) {
+        if (/^\d$/.test(c)) {
+          this.controlWordParam += c;
+        } else if (c === ' ') {
+          this.emitControlWord();
+          this.parserState = this.parseText;
+        } else {
+          this.emitControlWord();
+          this.parserState = this.parseText;
+          this.parseText(c);
+        }
+    },
+    
+    
+    
+    
+    emitText : function () {
+        if (this.text === '') {
+            return;
+        }
+        this.push({
+            type: 'text',
+            value: this.text,
+            pos: this.cpos,
+            row: this.row,
+            col: this.col
+        });
+        this.text = ''
+    },
+    emitControlWord : function ()
+    {
+        this.emitText();
+        if (this.controlWord === '') {
+            // do we want to track this - it seems just to cause problems.
+            //this.emitError('empty control word');
+        } else {
+            this.push({
+                  type: 'controlword',
+                  value: this.controlWord,
+                  param: this.controlWordParam !== '' && Number(this.controlWordParam),
+                  pos: this.cpos,
+                  row: this.row,
+                  col: this.col
+            });
+        }
+        this.controlWord = '';
+        this.controlWordParam = '';
+    },
+    emitStartGroup : function ()
+    {
+        this.emitText();
+        this.push({
+            type: 'groupstart',
+            pos: this.cpos,
+            row: this.row,
+            col: this.col
+        });
+    },
+    emitEndGroup : function ()
+    {
+        this.emitText();
+        this.push({
+            type: 'groupend',
+            pos: this.cpos,
+            row: this.row,
+            col: this.col
+        });
+    },
+    emitIgnorable : function ()
+    {
+        this.emitText();
+        this.push({
+            type: 'ignorable',
+            pos: this.cpos,
+            row: this.row,
+            col: this.col
+        });
+    },
+    emitHexChar : function ()
+    {
+        this.emitText();
+        this.push({
+            type: 'hexchar',
+            value: this.hexChar,
+            pos: this.cpos,
+            row: this.row,
+            col: this.col
+        });
+        this.hexChar = ''
+    },
+    emitError : function (message)
+    {
+      this.emitText();
+      this.push({
+            type: 'error',
+            value: message,
+            row: this.row,
+            col: this.col,
+            char: this.cpos //,
+            //stack: new Error().stack
+        });
+    },
+    emitEndParagraph : function () {
+        this.emitText();
+        this.push({
+            type: 'endparagraph',
+            pos: this.cpos,
+            row: this.row,
+            col: this.col
+        });
+    }
+     
+} ; 
+/**
+ * @class Roo.htmleditor.Filter
+ * Base Class for filtering htmleditor stuff. - do not use this directly - extend it.
+ * @cfg {DomElement} node The node to iterate and filter
+ * @cfg {boolean|String|Array} tag Tags to replace 
+ * @constructor
+ * Create a new Filter.
+ * @param {Object} config Configuration options
+ */
+
+
+
+Roo.htmleditor.Filter = function(cfg) {
+    Roo.apply(this.cfg);
+    // this does not actually call walk as it's really just a abstract class
+}
+
+
+Roo.htmleditor.Filter.prototype = {
+    
+    node: false,
+    
+    tag: false,
+
+    // overrride to do replace comments.
+    replaceComment : false,
+    
+    // overrride to do replace or do stuff with tags..
+    replaceTag : false,
+    
+    walk : function(dom)
+    {
+        Roo.each( Array.from(dom.childNodes), function( e ) {
+            switch(true) {
+                
+                case e.nodeType == 8 &&  this.replaceComment  !== false: // comment
+                    this.replaceComment(e);
+                    return;
+                
+                case e.nodeType != 1: //not a node.
+                    return;
+                
+                case this.tag === true: // everything
+                case e.tagName.indexOf(":") > -1 && typeof(this.tag) == 'object' && this.tag.indexOf(":") > -1:
+                case e.tagName.indexOf(":") > -1 && typeof(this.tag) == 'string' && this.tag == ":":
+                case typeof(this.tag) == 'object' && this.tag.indexOf(e.tagName) > -1: // array and it matches.
+                case typeof(this.tag) == 'string' && this.tag == e.tagName: // array and it matches.
+                    if (this.replaceTag && false === this.replaceTag(e)) {
+                        return;
+                    }
+                    if (e.hasChildNodes()) {
+                        this.walk(e);
+                    }
+                    return;
+                
+                default:    // tags .. that do not match.
+                    if (e.hasChildNodes()) {
+                        this.walk(e);
+                    }
+            }
+            
+        }, this);
+        
+    },
+    
+    
+    removeNodeKeepChildren : function( node)
+    {
+    
+        ar = Array.from(node.childNodes);
+        for (var i = 0; i < ar.length; i++) {
+         
+            node.removeChild(ar[i]);
+            // what if we need to walk these???
+            node.parentNode.insertBefore(ar[i], node);
+           
+        }
+        node.parentNode.removeChild(node);
+    }
+}; 
+
+/**
+ * @class Roo.htmleditor.FilterAttributes
+ * clean attributes and  styles including http:// etc.. in attribute
+ * @constructor
+* Run a new Attribute Filter
+* @param {Object} config Configuration options
+ */
+Roo.htmleditor.FilterAttributes = function(cfg)
+{
+    Roo.apply(this, cfg);
+    this.attrib_black = this.attrib_black || [];
+    this.attrib_white = this.attrib_white || [];
+
+    this.attrib_clean = this.attrib_clean || [];
+    this.style_white = this.style_white || [];
+    this.style_black = this.style_black || [];
+    this.walk(cfg.node);
+}
+
+Roo.extend(Roo.htmleditor.FilterAttributes, Roo.htmleditor.Filter,
+{
+    tag: true, // all tags
+    
+    attrib_black : false, // array
+    attrib_clean : false,
+    attrib_white : false,
+
+    style_white : false,
+    style_black : false,
+     
+     
+    replaceTag : function(node)
+    {
+        if (!node.attributes || !node.attributes.length) {
+            return true;
+        }
+        
+        for (var i = node.attributes.length-1; i > -1 ; i--) {
+            var a = node.attributes[i];
+            //console.log(a);
+            if (this.attrib_white.length && this.attrib_white.indexOf(a.name.toLowerCase()) < 0) {
+                node.removeAttribute(a.name);
+                continue;
+            }
+            
+            
+            
+            if (a.name.toLowerCase().substr(0,2)=='on')  {
+                node.removeAttribute(a.name);
+                continue;
+            }
+            
+            
+            if (this.attrib_black.indexOf(a.name.toLowerCase()) > -1) {
+                node.removeAttribute(a.name);
+                continue;
+            }
+            if (this.attrib_clean.indexOf(a.name.toLowerCase()) > -1) {
+                this.cleanAttr(node,a.name,a.value); // fixme..
+                continue;
+            }
+            if (a.name == 'style') {
+                this.cleanStyle(node,a.name,a.value);
+                continue;
+            }
+            /// clean up MS crap..
+            // tecnically this should be a list of valid class'es..
+            
+            
+            if (a.name == 'class') {
+                if (a.value.match(/^Mso/)) {
+                    node.removeAttribute('class');
+                }
+                
+                if (a.value.match(/^body$/)) {
+                    node.removeAttribute('class');
+                }
+                continue;
+            }
+            
+            
+            // style cleanup!?
+            // class cleanup?
+            
+        }
+        return true; // clean children
+    },
+        
+    cleanAttr: function(node, n,v)
+    {
+        
+        if (v.match(/^\./) || v.match(/^\//)) {
+            return;
+        }
+        if (v.match(/^(http|https):\/\//)
+            || v.match(/^mailto:/) 
+            || v.match(/^ftp:/)
+            || v.match(/^data:/)
+            ) {
+            return;
+        }
+        if (v.match(/^#/)) {
+            return;
+        }
+        if (v.match(/^\{/)) { // allow template editing.
+            return;
+        }
+//            Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
+        node.removeAttribute(n);
+        
+    },
+    cleanStyle : function(node,  n,v)
+    {
+        if (v.match(/expression/)) { //XSS?? should we even bother..
+            node.removeAttribute(n);
+            return;
+        }
+        
+        var parts = v.split(/;/);
+        var clean = [];
+        
+        Roo.each(parts, function(p) {
+            p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
+            if (!p.length) {
+                return true;
+            }
+            var l = p.split(':').shift().replace(/\s+/g,'');
+            l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
+            
+            if ( this.style_black.length && (this.style_black.indexOf(l) > -1 || this.style_black.indexOf(l.toLowerCase()) > -1)) {
+                return true;
+            }
+            //Roo.log()
+            // only allow 'c whitelisted system attributes'
+            if ( this.style_white.length &&  style_white.indexOf(l) < 0 && style_white.indexOf(l.toLowerCase()) < 0 ) {
+                return true;
+            }
+            
+            
+            clean.push(p);
+            return true;
+        },this);
+        if (clean.length) { 
+            node.setAttribute(n, clean.join(';'));
+        } else {
+            node.removeAttribute(n);
+        }
+        
+    }
+        
+        
+        
+    
+});/**
+ * @class Roo.htmleditor.FilterBlack
+ * remove blacklisted elements.
+ * @constructor
+ * Run a new Blacklisted Filter
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.FilterBlack = function(cfg)
+{
+    Roo.apply(this, cfg);
+    this.walk(cfg.node);
+}
+
+Roo.extend(Roo.htmleditor.FilterBlack, Roo.htmleditor.Filter,
+{
+    tag : true, // all elements.
+   
+    replaceTag : function(n)
+    {
+        n.parentNode.removeChild(n);
+    }
+});
+/**
+ * @class Roo.htmleditor.FilterComment
+ * remove comments.
+ * @constructor
+* Run a new Comments Filter
+* @param {Object} config Configuration options
+ */
+Roo.htmleditor.FilterComment = function(cfg)
+{
+    this.walk(cfg.node);
+}
+
+Roo.extend(Roo.htmleditor.FilterComment, Roo.htmleditor.Filter,
+{
+  
+    replaceComment : function(n)
+    {
+        n.parentNode.removeChild(n);
+    }
+});/**
+ * @class Roo.htmleditor.FilterKeepChildren
+ * remove tags but keep children
+ * @constructor
+ * Run a new Keep Children Filter
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.FilterKeepChildren = function(cfg)
+{
+    Roo.apply(this, cfg);
+    if (this.tag === false) {
+        return; // dont walk.. (you can use this to use this just to do a child removal on a single tag )
+    }
+    // hacky?
+    if ((typeof(this.tag) == 'object' && this.tag.indexOf(":") > -1)) {
+        this.cleanNamespace = true;
+    }
+        
+    this.walk(cfg.node);
+}
+
+Roo.extend(Roo.htmleditor.FilterKeepChildren, Roo.htmleditor.FilterBlack,
+{
+    cleanNamespace : false, // should really be an option, rather than using ':' inside of this tag.
+  
+    replaceTag : function(node)
+    {
+        // walk children...
+        //Roo.log(node.tagName);
+        var ar = Array.from(node.childNodes);
+        //remove first..
+        
+        for (var i = 0; i < ar.length; i++) {
+            var e = ar[i];
+            if (e.nodeType == 1) {
+                if (
+                    (typeof(this.tag) == 'object' && this.tag.indexOf(e.tagName) > -1)
+                    || // array and it matches
+                    (typeof(this.tag) == 'string' && this.tag == e.tagName)
+                    ||
+                    (e.tagName.indexOf(":") > -1 && typeof(this.tag) == 'object' && this.tag.indexOf(":") > -1)
+                    ||
+                    (e.tagName.indexOf(":") > -1 && typeof(this.tag) == 'string' && this.tag == ":")
+                ) {
+                    this.replaceTag(ar[i]); // child is blacklisted as well...
+                    continue;
+                }
+            }
+        }  
+        ar = Array.from(node.childNodes);
+        for (var i = 0; i < ar.length; i++) {
+         
+            node.removeChild(ar[i]);
+            // what if we need to walk these???
+            node.parentNode.insertBefore(ar[i], node);
+            if (this.tag !== false) {
+                this.walk(ar[i]);
+                
+            }
+        }
+        //Roo.log("REMOVE:" + node.tagName);
+        node.parentNode.removeChild(node);
+        return false; // don't walk children
+        
+        
+    }
+});/**
+ * @class Roo.htmleditor.FilterParagraph
+ * paragraphs cause a nightmare for shared content - this filter is designed to be called ? at various points when editing
+ * like on 'push' to remove the <p> tags and replace them with line breaks.
+ * @constructor
+ * Run a new Paragraph Filter
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.FilterParagraph = function(cfg)
+{
+    // no need to apply config.
+    this.walk(cfg.node);
+}
+
+Roo.extend(Roo.htmleditor.FilterParagraph, Roo.htmleditor.Filter,
+{
+    
+     
+    tag : 'P',
+    
+     
+    replaceTag : function(node)
+    {
+        
+        if (node.childNodes.length == 1 &&
+            node.childNodes[0].nodeType == 3 &&
+            node.childNodes[0].textContent.trim().length < 1
+            ) {
+            // remove and replace with '<BR>';
+            node.parentNode.replaceChild(node.ownerDocument.createElement('BR'),node);
+            return false; // no need to walk..
+        }
+        var ar = Array.from(node.childNodes);
+        for (var i = 0; i < ar.length; i++) {
+            node.removeChild(ar[i]);
+            // what if we need to walk these???
+            node.parentNode.insertBefore(ar[i], node);
+        }
+        // now what about this?
+        // <p> &nbsp; </p>
+        
+        // double BR.
+        node.parentNode.insertBefore(node.ownerDocument.createElement('BR'), node);
+        node.parentNode.insertBefore(node.ownerDocument.createElement('BR'), node);
+        node.parentNode.removeChild(node);
+        
+        return false;
+
+    }
+    
+});/**
+ * @class Roo.htmleditor.FilterSpan
+ * filter span's with no attributes out..
+ * @constructor
+ * Run a new Span Filter
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.FilterSpan = function(cfg)
+{
+    // no need to apply config.
+    this.walk(cfg.node);
+}
+
+Roo.extend(Roo.htmleditor.FilterSpan, Roo.htmleditor.FilterKeepChildren,
+{
+     
+    tag : 'SPAN',
+     
+    replaceTag : function(node)
+    {
+        if (node.attributes && node.attributes.length > 0) {
+            return true; // walk if there are any.
+        }
+        Roo.htmleditor.FilterKeepChildren.prototype.replaceTag.call(this, node);
+        return false;
+     
+    }
+    
+});/**
+ * @class Roo.htmleditor.FilterTableWidth
+  try and remove table width data - as that frequently messes up other stuff.
+ * 
+ *      was cleanTableWidths.
+ *
+ * Quite often pasting from word etc.. results in tables with column and widths.
+ * This does not work well on fluid HTML layouts - like emails. - so this code should hunt an destroy them..
+ *
+ * @constructor
+ * Run a new Table Filter
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.FilterTableWidth = function(cfg)
+{
+    // no need to apply config.
+    this.tag = ['TABLE', 'TD', 'TR', 'TH', 'THEAD', 'TBODY' ];
+    this.walk(cfg.node);
+}
+
+Roo.extend(Roo.htmleditor.FilterTableWidth, Roo.htmleditor.Filter,
+{
+     
+     
+    
+    replaceTag: function(node) {
+        
+        
+      
+        if (node.hasAttribute('width')) {
+            node.removeAttribute('width');
+        }
+        
+         
+        if (node.hasAttribute("style")) {
+            // pretty basic...
+            
+            var styles = node.getAttribute("style").split(";");
+            var nstyle = [];
+            Roo.each(styles, function(s) {
+                if (!s.match(/:/)) {
+                    return;
+                }
+                var kv = s.split(":");
+                if (kv[0].match(/^\s*(width|min-width)\s*$/)) {
+                    return;
+                }
+                // what ever is left... we allow.
+                nstyle.push(s);
+            });
+            node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
+            if (!nstyle.length) {
+                node.removeAttribute('style');
+            }
+        }
+        
+        return true; // continue doing children..
+    }
+});/**
+ * @class Roo.htmleditor.FilterWord
+ * try and clean up all the mess that Word generates.
+ * 
+ * This is the 'nice version' - see 'Heavy' that white lists a very short list of elements, and multi-filters 
+ * @constructor
+ * Run a new Span Filter
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.FilterWord = function(cfg)
+{
+    // no need to apply config.
+    this.replaceDocBullets(cfg.node);
+    
+    this.replaceAname(cfg.node);
+    // this is disabled as the removal is done by other filters;
+   // this.walk(cfg.node);
+    this.replaceImageTable(cfg.node);
+    
+}
+
+Roo.extend(Roo.htmleditor.FilterWord, Roo.htmleditor.Filter,
+{
+    tag: true,
+     
+    
+    /**
+     * Clean up MS wordisms...
+     */
+    replaceTag : function(node)
+    {
+         
+        // no idea what this does - span with text, replaceds with just text.
+        if(
+                node.nodeName == 'SPAN' &&
+                !node.hasAttributes() &&
+                node.childNodes.length == 1 &&
+                node.firstChild.nodeName == "#text"  
+        ) {
+            var textNode = node.firstChild;
+            node.removeChild(textNode);
+            if (node.getAttribute('lang') != 'zh-CN') {   // do not space pad on chinese characters..
+                node.parentNode.insertBefore(node.ownerDocument.createTextNode(" "), node);
+            }
+            node.parentNode.insertBefore(textNode, node);
+            if (node.getAttribute('lang') != 'zh-CN') {   // do not space pad on chinese characters..
+                node.parentNode.insertBefore(node.ownerDocument.createTextNode(" ") , node);
+            }
+            
+            node.parentNode.removeChild(node);
+            return false; // dont do chidren - we have remove our node - so no need to do chdhilren?
+        }
+        
+   
+        
+        if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
+            node.parentNode.removeChild(node);
+            return false; // dont do chidlren
+        }
+        //Roo.log(node.tagName);
+        // remove - but keep children..
+        if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|v:|font)/)) {
+            //Roo.log('-- removed');
+            while (node.childNodes.length) {
+                var cn = node.childNodes[0];
+                node.removeChild(cn);
+                node.parentNode.insertBefore(cn, node);
+                // move node to parent - and clean it..
+                if (cn.nodeType == 1) {
+                    this.replaceTag(cn);
+                }
+                
+            }
+            node.parentNode.removeChild(node);
+            /// no need to iterate chidlren = it's got none..
+            //this.iterateChildren(node, this.cleanWord);
+            return false; // no need to iterate children.
+        }
+        // clean styles
+        if (node.className.length) {
+            
+            var cn = node.className.split(/\W+/);
+            var cna = [];
+            Roo.each(cn, function(cls) {
+                if (cls.match(/Mso[a-zA-Z]+/)) {
+                    return;
+                }
+                cna.push(cls);
+            });
+            node.className = cna.length ? cna.join(' ') : '';
+            if (!cna.length) {
+                node.removeAttribute("class");
+            }
+        }
+        
+        if (node.hasAttribute("lang")) {
+            node.removeAttribute("lang");
+        }
+        
+        if (node.hasAttribute("style")) {
+            
+            var styles = node.getAttribute("style").split(";");
+            var nstyle = [];
+            Roo.each(styles, function(s) {
+                if (!s.match(/:/)) {
+                    return;
+                }
+                var kv = s.split(":");
+                if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
+                    return;
+                }
+                // what ever is left... we allow.
+                nstyle.push(s);
+            });
+            node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
+            if (!nstyle.length) {
+                node.removeAttribute('style');
+            }
+        }
+        return true; // do children
+        
+        
+        
+    },
+    
+    styleToObject: function(node)
+    {
+        var styles = (node.getAttribute("style") || '').split(";");
+        var ret = {};
+        Roo.each(styles, function(s) {
+            if (!s.match(/:/)) {
+                return;
+            }
+            var kv = s.split(":");
+             
+            // what ever is left... we allow.
+            ret[kv[0].trim()] = kv[1];
+        });
+        return ret;
+    },
+    
+    
+    replaceAname : function (doc)
+    {
+        // replace all the a/name without..
+        var aa = Array.from(doc.getElementsByTagName('a'));
+        for (var i = 0; i  < aa.length; i++) {
+            var a = aa[i];
+            if (a.hasAttribute("name")) {
+                a.removeAttribute("name");
+            }
+            if (a.hasAttribute("href")) {
+                continue;
+            }
+            // reparent children.
+            this.removeNodeKeepChildren(a);
+            
+        }
+        
+        
+        
+    },
+
+    
+    
+    replaceDocBullets : function(doc)
+    {
+        // this is a bit odd - but it appears some indents use ql-indent-1
+         //Roo.log(doc.innerHTML);
+        
+        var listpara = Array.from(doc.getElementsByClassName('MsoListParagraphCxSpFirst'));
+        for( var i = 0; i < listpara.length; i ++) {
+            listpara[i].className = "MsoListParagraph";
+        }
+        
+        listpara =  Array.from(doc.getElementsByClassName('MsoListParagraphCxSpMiddle'));
+        for( var i = 0; i < listpara.length; i ++) {
+            listpara[i].className = "MsoListParagraph";
+        }
+        listpara =  Array.from(doc.getElementsByClassName('MsoListParagraphCxSpLast'));
+        for( var i = 0; i < listpara.length; i ++) {
+            listpara[i].className = "MsoListParagraph";
+        }
+        listpara =  Array.from(doc.getElementsByClassName('ql-indent-1'));
+        for( var i = 0; i < listpara.length; i ++) {
+            listpara[i].className = "MsoListParagraph";
+        }
+        
+        // this is a bit hacky - we had one word document where h2 had a miso-list attribute.
+        var htwo =  Array.from(doc.getElementsByTagName('h2'));
+        for( var i = 0; i < htwo.length; i ++) {
+            if (htwo[i].hasAttribute('style') && htwo[i].getAttribute('style').match(/mso-list:/)) {
+                htwo[i].className = "MsoListParagraph";
+            }
+        }
+        listpara =  Array.from(doc.getElementsByClassName('MsoNormal'));
+        for( var i = 0; i < listpara.length; i ++) {
+            if (listpara[i].hasAttribute('style') && listpara[i].getAttribute('style').match(/mso-list:/)) {
+                listpara[i].className = "MsoListParagraph";
+            } else {
+                listpara[i].className = "MsoNormalx";
+            }
+        }
+       
+        listpara = doc.getElementsByClassName('MsoListParagraph');
+        // Roo.log(doc.innerHTML);
+        
+        
+        
+        while(listpara.length) {
+            
+            this.replaceDocBullet(listpara.item(0));
+        }
+      
+    },
+    
+     
+    
+    replaceDocBullet : function(p)
+    {
+        // gather all the siblings.
+        var ns = p,
+            parent = p.parentNode,
+            doc = parent.ownerDocument,
+            items = [];
+         
+        //Roo.log("Parsing: " + p.innerText)    ;
+        var listtype = 'ul';   
+        while (ns) {
+            if (ns.nodeType != 1) {
+                ns = ns.nextSibling;
+                continue;
+            }
+            if (!ns.className.match(/(MsoListParagraph|ql-indent-1)/i)) {
+                //Roo.log("Missing para r q1indent - got:" + ns.className);
+                break;
+            }
+            var spans = ns.getElementsByTagName('span');
+            
+            if (ns.hasAttribute('style') && ns.getAttribute('style').match(/mso-list/)) {
+                items.push(ns);
+                ns = ns.nextSibling;
+                has_list = true;
+                if (!spans.length) {
+                    continue;
+                }
+                var ff = '';
+                var se = spans[0];
+                for (var i = 0; i < spans.length;i++) {
+                    se = spans[i];
+                    if (se.hasAttribute('style')  && se.hasAttribute('style') && se.style.fontFamily != '') {
+                        ff = se.style.fontFamily;
+                        break;
+                    }
+                }
+                 
+                    
+                //Roo.log("got font family: " + ff);
+                if (typeof(ff) != 'undefined' && !ff.match(/(Symbol|Wingdings)/) && "·o".indexOf(se.innerText.trim()) < 0) {
+                    listtype = 'ol';
+                }
+                
+                continue;
+            }
+            //Roo.log("no mso-list?");
+            
+            var spans = ns.getElementsByTagName('span');
+            if (!spans.length) {
+                break;
+            }
+            var has_list  = false;
+            for(var i = 0; i < spans.length; i++) {
+                if (spans[i].hasAttribute('style') && spans[i].getAttribute('style').match(/mso-list/)) {
+                    has_list = true;
+                    break;
+                }
+            }
+            if (!has_list) {
+                break;
+            }
+            items.push(ns);
+            ns = ns.nextSibling;
+            
+            
+        }
+        if (!items.length) {
+            ns.className = "";
+            return;
+        }
+        
+        var ul = parent.ownerDocument.createElement(listtype); // what about number lists...
+        parent.insertBefore(ul, p);
+        var lvl = 0;
+        var stack = [ ul ];
+        var last_li = false;
+        
+        var margin_to_depth = {};
+        max_margins = -1;
+        
+        items.forEach(function(n, ipos) {
+            //Roo.log("got innertHMLT=" + n.innerHTML);
+            
+            var spans = n.getElementsByTagName('span');
+            if (!spans.length) {
+                //Roo.log("No spans found");
+                 
+                parent.removeChild(n);
+                
+                
+                return; // skip it...
+            }
+           
+                
+            var num = 1;
+            var style = {};
+            for(var i = 0; i < spans.length; i++) {
+            
+                style = this.styleToObject(spans[i]);
+                if (typeof(style['mso-list']) == 'undefined') {
+                    continue;
+                }
+                if (listtype == 'ol') {
+                   num = spans[i].innerText.replace(/[^0-9]+]/g,'')  * 1;
+                }
+                spans[i].parentNode.removeChild(spans[i]); // remove the fake bullet.
+                break;
+            }
+            //Roo.log("NOW GOT innertHMLT=" + n.innerHTML);
+            style = this.styleToObject(n); // mo-list is from the parent node.
+            if (typeof(style['mso-list']) == 'undefined') {
+                //Roo.log("parent is missing level");
+                  
+                parent.removeChild(n);
+                 
+                return;
+            }
+            
+            var margin = style['margin-left'];
+            if (typeof(margin_to_depth[margin]) == 'undefined') {
+                max_margins++;
+                margin_to_depth[margin] = max_margins;
+            }
+            nlvl = margin_to_depth[margin] ;
+             
+            if (nlvl > lvl) {
+                //new indent
+                var nul = doc.createElement(listtype); // what about number lists...
+                if (!last_li) {
+                    last_li = doc.createElement('li');
+                    stack[lvl].appendChild(last_li);
+                }
+                last_li.appendChild(nul);
+                stack[nlvl] = nul;
+                
+            }
+            lvl = nlvl;
+            
+            // not starting at 1..
+            if (!stack[nlvl].hasAttribute("start") && listtype == "ol") {
+                stack[nlvl].setAttribute("start", num);
+            }
+            
+            var nli = stack[nlvl].appendChild(doc.createElement('li'));
+            last_li = nli;
+            nli.innerHTML = n.innerHTML;
+            //Roo.log("innerHTML = " + n.innerHTML);
+            parent.removeChild(n);
+            
+             
+             
+            
+        },this);
+        
+        
+        
+        
+    },
+    
+    replaceImageTable : function(doc)
+    {
+         /*
+          <table cellpadding=0 cellspacing=0 align=left>
+  <tr>
+   <td width=423 height=0></td>
+  </tr>
+  <tr>
+   <td></td>
+   <td><img width=601 height=401
+   src="file:///C:/Users/Alan/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg"
+   v:shapes="Picture_x0020_2"></td>
+  </tr>
+ </table>
+ */
+        var imgs = Array.from(doc.getElementsByTagName('img'));
+        Roo.each(imgs, function(img) {
+            var td = img.parentNode;
+            if (td.nodeName !=  'TD') {
+                return;
+            }
+            var tr = td.parentNode;
+            if (tr.nodeName !=  'TR') {
+                return;
+            }
+            var tbody = tr.parentNode;
+            if (tbody.nodeName !=  'TBODY') {
+                return;
+            }
+            var table = tbody.parentNode;
+            if (table.nodeName !=  'TABLE') {
+                return;
+            }
+            // first row..
+            
+            if (table.getElementsByTagName('tr').length != 2) {
+                return;
+            }
+            if (table.getElementsByTagName('td').length != 3) {
+                return;
+            }
+            if (table.innerText.trim() != '') {
+                return;
+            }
+            var p = table.parentNode;
+            img.parentNode.removeChild(img);
+            p.insertBefore(img, table);
+            p.removeChild(table);
+            
+            
+            
+        });
+        
+      
+    }
+    
+});
+/**
+ * @class Roo.htmleditor.FilterStyleToTag
+ * part of the word stuff... - certain 'styles' should be converted to tags.
+ * eg.
+ *   font-weight: bold -> bold
+ *   ?? super / subscrit etc..
+ * 
+ * @constructor
+* Run a new style to tag filter.
+* @param {Object} config Configuration options
+ */
+Roo.htmleditor.FilterStyleToTag = function(cfg)
+{
+    
+    this.tags = {
+        B  : [ 'fontWeight' , 'bold'],
+        I :  [ 'fontStyle' , 'italic'],
+        //pre :  [ 'font-style' , 'italic'],
+        // h1.. h6 ?? font-size?
+        SUP : [ 'verticalAlign' , 'super' ],
+        SUB : [ 'verticalAlign' , 'sub' ]
+        
+        
+    };
+    
+    Roo.apply(this, cfg);
+     
+    
+    this.walk(cfg.node);
+    
+    
+    
+}
+
+
+Roo.extend(Roo.htmleditor.FilterStyleToTag, Roo.htmleditor.Filter,
+{
+    tag: true, // all tags
+    
+    tags : false,
+    
+    
+    replaceTag : function(node)
+    {
+        
+        
+        if (node.getAttribute("style") === null) {
+            return true;
+        }
+        var inject = [];
+        for (var k in this.tags) {
+            if (node.style[this.tags[k][0]] == this.tags[k][1]) {
+                inject.push(k);
+                node.style.removeProperty(this.tags[k][0]);
+            }
+        }
+        if (!inject.length) {
+            return true; 
+        }
+        var cn = Array.from(node.childNodes);
+        var nn = node;
+        Roo.each(inject, function(t) {
+            var nc = node.ownerDocument.createElement(t);
+            nn.appendChild(nc);
+            nn = nc;
+        });
+        for(var i = 0;i < cn.length;cn++) {
+            node.removeChild(cn[i]);
+            nn.appendChild(cn[i]);
+        }
+        return true /// iterate thru
+    }
+    
+})/**
+ * @class Roo.htmleditor.FilterLongBr
+ * BR/BR/BR - keep a maximum of 2...
+ * @constructor
+ * Run a new Long BR Filter
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.FilterLongBr = function(cfg)
+{
+    // no need to apply config.
+    this.walk(cfg.node);
+}
+
+Roo.extend(Roo.htmleditor.FilterLongBr, Roo.htmleditor.Filter,
+{
+    
+     
+    tag : 'BR',
+    
+     
+    replaceTag : function(node)
+    {
+        
+        var ps = node.nextSibling;
+        while (ps && ps.nodeType == 3 && ps.nodeValue.trim().length < 1) {
+            ps = ps.nextSibling;
+        }
+        
+        if (!ps &&  [ 'TD', 'TH', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ].indexOf(node.parentNode.tagName) > -1) { 
+            node.parentNode.removeChild(node); // remove last BR inside one fo these tags
+            return false;
+        }
+        
+        if (!ps || ps.nodeType != 1) {
+            return false;
+        }
+        
+        if (!ps || ps.tagName != 'BR') {
+           
+            return false;
+        }
+        
+        
+        
+        
+        
+        if (!node.previousSibling) {
+            return false;
+        }
+        var ps = node.previousSibling;
+        
+        while (ps && ps.nodeType == 3 && ps.nodeValue.trim().length < 1) {
+            ps = ps.previousSibling;
+        }
+        if (!ps || ps.nodeType != 1) {
+            return false;
+        }
+        // if header or BR before.. then it's a candidate for removal.. - as we only want '2' of these..
+        if (!ps || [ 'BR', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ].indexOf(ps.tagName) < 0) {
+            return false;
+        }
+        
+        node.parentNode.removeChild(node); // remove me...
+        
+        return false; // no need to do children
+
+    }
+    
+}); 
+
+/**
+ * @class Roo.htmleditor.FilterBlock
+ * removes id / data-block and contenteditable that are associated with blocks
+ * usage should be done on a cloned copy of the dom
+ * @constructor
+* Run a new Attribute Filter { node : xxxx }}
+* @param {Object} config Configuration options
+ */
+Roo.htmleditor.FilterBlock = function(cfg)
+{
+    Roo.apply(this, cfg);
+    var qa = cfg.node.querySelectorAll;
+    this.removeAttributes('data-block');
+    this.removeAttributes('contenteditable');
+    this.removeAttributes('id');
+    
+}
+
+Roo.apply(Roo.htmleditor.FilterBlock.prototype,
+{
+    node: true, // all tags
+     
+     
+    removeAttributes : function(attr)
+    {
+        var ar = this.node.querySelectorAll('*[' + attr + ']');
+        for (var i =0;i<ar.length;i++) {
+            ar[i].removeAttribute(attr);
+        }
+    }
+        
+        
+        
+    
+});
+/***
+ * This is based loosely on tinymce 
+ * @class Roo.htmleditor.TidySerializer
+ * https://github.com/thorn0/tinymce.html/blob/master/tinymce.html.js
+ * @constructor
+ * @method Serializer
+ * @param {Object} settings Name/value settings object.
+ */
+
+
+Roo.htmleditor.TidySerializer = function(settings)
+{
+    Roo.apply(this, settings);
+    
+    this.writer = new Roo.htmleditor.TidyWriter(settings);
+    
+    
+
+};
+Roo.htmleditor.TidySerializer.prototype = {
+    
+    /**
+     * @param {boolean} inner do the inner of the node.
+     */
+    inner : false,
+    
+    writer : false,
+    
+    /**
+    * Serializes the specified node into a string.
+    *
+    * @example
+    * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
+    * @method serialize
+    * @param {DomElement} node Node instance to serialize.
+    * @return {String} String with HTML based on DOM tree.
+    */
+    serialize : function(node) {
+        
+        // = settings.validate;
+        var writer = this.writer;
+        var self  = this;
+        this.handlers = {
+            // #text
+            3: function(node) {
+                
+                writer.text(node.nodeValue, node);
+            },
+            // #comment
+            8: function(node) {
+                writer.comment(node.nodeValue);
+            },
+            // Processing instruction
+            7: function(node) {
+                writer.pi(node.name, node.nodeValue);
+            },
+            // Doctype
+            10: function(node) {
+                writer.doctype(node.nodeValue);
+            },
+            // CDATA
+            4: function(node) {
+                writer.cdata(node.nodeValue);
+            },
+            // Document fragment
+            11: function(node) {
+                node = node.firstChild;
+                if (!node) {
+                    return;
+                }
+                while(node) {
+                    self.walk(node);
+                    node = node.nextSibling
+                }
+            }
+        };
+        writer.reset();
+        1 != node.nodeType || this.inner ? this.handlers[11](node) : this.walk(node);
+        return writer.getContent();
+    },
+
+    walk: function(node)
+    {
+        var attrName, attrValue, sortedAttrs, i, l, elementRule,
+            handler = this.handlers[node.nodeType];
+            
+        if (handler) {
+            handler(node);
+            return;
+        }
+    
+        var name = node.nodeName;
+        var isEmpty = node.childNodes.length < 1;
+      
+        var writer = this.writer;
+        var attrs = node.attributes;
+        // Sort attributes
+        
+        writer.start(node.nodeName, attrs, isEmpty, node);
+        if (isEmpty) {
+            return;
+        }
+        node = node.firstChild;
+        if (!node) {
+            writer.end(name);
+            return;
+        }
+        while (node) {
+            this.walk(node);
+            node = node.nextSibling;
+        }
+        writer.end(name);
+        
+    
+    }
+    // Serialize element and treat all non elements as fragments
+   
+}; 
+
+/***
+ * This is based loosely on tinymce 
+ * @class Roo.htmleditor.TidyWriter
+ * https://github.com/thorn0/tinymce.html/blob/master/tinymce.html.js
+ *
+ * Known issues?
+ * - not tested much with 'PRE' formated elements.
+ * 
+ *
+ *
+ */
+
+Roo.htmleditor.TidyWriter = function(settings)
+{
+    
+    // indent, indentBefore, indentAfter, encode, htmlOutput, html = [];
+    Roo.apply(this, settings);
+    this.html = [];
+    this.state = [];
+     
+    this.encode = Roo.htmleditor.TidyEntities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
+  
+}
+Roo.htmleditor.TidyWriter.prototype = {
+
+    state : false,
+    
+    indent :  '  ',
+    
+    // part of state...
+    indentstr : '',
+    in_pre: false,
+    in_inline : false,
+    last_inline : false,
+    encode : false,
+     
+    
+            /**
+    * Writes the a start element such as <p id="a">.
+    *
+    * @method start
+    * @param {String} name Name of the element.
+    * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
+    * @param {Boolean} empty Optional empty state if the tag should end like <br />.
+    */
+    start: function(name, attrs, empty, node)
+    {
+        var i, l, attr, value;
+        
+        // there are some situations where adding line break && indentation will not work. will not work.
+        // <span / b / i ... formating?
+        
+        var in_inline = this.in_inline || Roo.htmleditor.TidyWriter.inline_elements.indexOf(name) > -1;
+        var in_pre    = this.in_pre    || Roo.htmleditor.TidyWriter.whitespace_elements.indexOf(name) > -1;
+        
+        var is_short   = empty ? Roo.htmleditor.TidyWriter.shortend_elements.indexOf(name) > -1 : false;
+        
+        var add_lb = name == 'BR' ? false : in_inline;
+        
+        if (!add_lb && !this.in_pre && this.lastElementEndsWS()) {
+            i_inline = false;
+        }
+
+        var indentstr =  this.indentstr;
+        
+        // e_inline = elements that can be inline, but still allow \n before and after?
+        // only 'BR' ??? any others?
+        
+        // ADD LINE BEFORE tage
+        if (!this.in_pre) {
+            if (in_inline) {
+                //code
+                if (name == 'BR') {
+                    this.addLine();
+                } else if (this.lastElementEndsWS()) {
+                    this.addLine();
+                } else{
+                    // otherwise - no new line. (and dont indent.)
+                    indentstr = '';
+                }
+                
+            } else {
+                this.addLine();
+            }
+        } else {
+            indentstr = '';
+        }
+        
+        this.html.push(indentstr + '<', name.toLowerCase());
+        
+        if (attrs) {
+            for (i = 0, l = attrs.length; i < l; i++) {
+                attr = attrs[i];
+                this.html.push(' ', attr.name, '="', this.encode(attr.value, true), '"');
+            }
+        }
+     
+        if (empty) {
+            if (is_short) {
+                this.html[this.html.length] = '/>';
+            } else {
+                this.html[this.html.length] = '></' + name.toLowerCase() + '>';
+            }
+            var e_inline = name == 'BR' ? false : this.in_inline;
+            
+            if (!e_inline && !this.in_pre) {
+                this.addLine();
+            }
+            return;
+        
+        }
+        // not empty..
+        this.html[this.html.length] = '>';
+        
+        // there is a special situation, where we need to turn on in_inline - if any of the imediate chidlren are one of these.
+        /*
+        if (!in_inline && !in_pre) {
+            var cn = node.firstChild;
+            while(cn) {
+                if (Roo.htmleditor.TidyWriter.inline_elements.indexOf(cn.nodeName) > -1) {
+                    in_inline = true
+                    break;
+                }
+                cn = cn.nextSibling;
+            }
+             
+        }
+        */
+        
+        
+        this.pushState({
+            indentstr : in_pre   ? '' : (this.indentstr + this.indent),
+            in_pre : in_pre,
+            in_inline :  in_inline
+        });
+        // add a line after if we are not in a
+        
+        if (!in_inline && !in_pre) {
+            this.addLine();
+        }
+        
+            
+         
+        
+    },
+    
+    lastElementEndsWS : function()
+    {
+        var value = this.html.length > 0 ? this.html[this.html.length-1] : false;
+        if (value === false) {
+            return true;
+        }
+        return value.match(/\s+$/);
+        
+    },
+    
+    /**
+     * Writes the a end element such as </p>.
+     *
+     * @method end
+     * @param {String} name Name of the element.
+     */
+    end: function(name) {
+        var value;
+        this.popState();
+        var indentstr = '';
+        var in_inline = this.in_inline || Roo.htmleditor.TidyWriter.inline_elements.indexOf(name) > -1;
+        
+        if (!this.in_pre && !in_inline) {
+            this.addLine();
+            indentstr  = this.indentstr;
+        }
+        this.html.push(indentstr + '</', name.toLowerCase(), '>');
+        this.last_inline = in_inline;
+        
+        // pop the indent state..
+    },
+    /**
+     * Writes a text node.
+     *
+     * In pre - we should not mess with the contents.
+     * 
+     *
+     * @method text
+     * @param {String} text String to write out.
+     * @param {Boolean} raw Optional raw state if true the contents wont get encoded.
+     */
+    text: function(in_text, node)
+    {
+        // if not in whitespace critical
+        if (in_text.length < 1) {
+            return;
+        }
+        var text = new XMLSerializer().serializeToString(document.createTextNode(in_text)); // escape it properly?
+        
+        if (this.in_pre) {
+            this.html[this.html.length] =  text;
+            return;   
+        }
+        
+        if (this.in_inline) {
+            text = text.replace(/\s+/g,' '); // all white space inc line breaks to a slingle' '
+            if (text != ' ') {
+                text = text.replace(/\s+/,' ');  // all white space to single white space
+                
+                    
+                // if next tag is '<BR>', then we can trim right..
+                if (node.nextSibling &&
+                    node.nextSibling.nodeType == 1 &&
+                    node.nextSibling.nodeName == 'BR' )
+                {
+                    text = text.replace(/\s+$/g,'');
+                }
+                // if previous tag was a BR, we can also trim..
+                if (node.previousSibling &&
+                    node.previousSibling.nodeType == 1 &&
+                    node.previousSibling.nodeName == 'BR' )
+                {
+                    text = this.indentstr +  text.replace(/^\s+/g,'');
+                }
+                if (text.match(/\n/)) {
+                    text = text.replace(
+                        /(?![^\n]{1,64}$)([^\n]{1,64})\s/g, '$1\n' + this.indentstr
+                    );
+                    // remoeve the last whitespace / line break.
+                    text = text.replace(/\n\s+$/,'');
+                }
+                // repace long lines
+                
+            }
+             
+            this.html[this.html.length] =  text;
+            return;   
+        }
+        // see if previous element was a inline element.
+        var indentstr = this.indentstr;
+   
+        text = text.replace(/\s+/g," "); // all whitespace into single white space.
+        
+        // should trim left?
+        if (node.previousSibling &&
+            node.previousSibling.nodeType == 1 &&
+            Roo.htmleditor.TidyWriter.inline_elements.indexOf(node.previousSibling.nodeName) > -1)
+        {
+            indentstr = '';
+            
+        } else {
+            this.addLine();
+            text = text.replace(/^\s+/,''); // trim left
+          
+        }
+        // should trim right?
+        if (node.nextSibling &&
+            node.nextSibling.nodeType == 1 &&
+            Roo.htmleditor.TidyWriter.inline_elements.indexOf(node.nextSibling.nodeName) > -1)
+        {
+          // noop
+            
+        }  else {
+            text = text.replace(/\s+$/,''); // trim right
+        }
+         
+              
+        
+        
+        
+        if (text.length < 1) {
+            return;
+        }
+        if (!text.match(/\n/)) {
+            this.html.push(indentstr + text);
+            return;
+        }
+        
+        text = this.indentstr + text.replace(
+            /(?![^\n]{1,64}$)([^\n]{1,64})\s/g, '$1\n' + this.indentstr
+        );
+        // remoeve the last whitespace / line break.
+        text = text.replace(/\s+$/,''); 
+        
+        this.html.push(text);
+        
+        // split and indent..
+        
+        
+    },
+    /**
+     * Writes a cdata node such as <![CDATA[data]]>.
+     *
+     * @method cdata
+     * @param {String} text String to write out inside the cdata.
+     */
+    cdata: function(text) {
+        this.html.push('<![CDATA[', text, ']]>');
+    },
+    /**
+    * Writes a comment node such as <!-- Comment -->.
+    *
+    * @method cdata
+    * @param {String} text String to write out inside the comment.
+    */
+   comment: function(text) {
+       this.html.push('<!--', text, '-->');
+   },
+    /**
+     * Writes a PI node such as <?xml attr="value" ?>.
+     *
+     * @method pi
+     * @param {String} name Name of the pi.
+     * @param {String} text String to write out inside the pi.
+     */
+    pi: function(name, text) {
+        text ? this.html.push('<?', name, ' ', this.encode(text), '?>') : this.html.push('<?', name, '?>');
+        this.indent != '' && this.html.push('\n');
+    },
+    /**
+     * Writes a doctype node such as <!DOCTYPE data>.
+     *
+     * @method doctype
+     * @param {String} text String to write out inside the doctype.
+     */
+    doctype: function(text) {
+        this.html.push('<!DOCTYPE', text, '>', this.indent != '' ? '\n' : '');
+    },
+    /**
+     * Resets the internal buffer if one wants to reuse the writer.
+     *
+     * @method reset
+     */
+    reset: function() {
+        this.html.length = 0;
+        this.state = [];
+        this.pushState({
+            indentstr : '',
+            in_pre : false, 
+            in_inline : false
+        })
+    },
+    /**
+     * Returns the contents that got serialized.
+     *
+     * @method getContent
+     * @return {String} HTML contents that got written down.
+     */
+    getContent: function() {
+        return this.html.join('').replace(/\n$/, '');
+    },
+    
+    pushState : function(cfg)
+    {
+        this.state.push(cfg);
+        Roo.apply(this, cfg);
+    },
+    
+    popState : function()
+    {
+        if (this.state.length < 1) {
+            return; // nothing to push
+        }
+        var cfg = {
+            in_pre: false,
+            indentstr : ''
+        };
+        this.state.pop();
+        if (this.state.length > 0) {
+            cfg = this.state[this.state.length-1]; 
+        }
+        Roo.apply(this, cfg);
+    },
+    
+    addLine: function()
+    {
+        if (this.html.length < 1) {
+            return;
+        }
+        
+        
+        var value = this.html[this.html.length - 1];
+        if (value.length > 0 && '\n' !== value) {
+            this.html.push('\n');
+        }
+    }
+    
+    
+//'pre script noscript style textarea video audio iframe object code'
+// shortended... 'area base basefont br col frame hr img input isindex link  meta param embed source wbr track');
+// inline 
+};
+
+Roo.htmleditor.TidyWriter.inline_elements = [
+        'SPAN','STRONG','B','EM','I','FONT','STRIKE','U','VAR',
+        'CITE','DFN','CODE','MARK','Q','SUP','SUB','SAMP', 'A'
+];
+Roo.htmleditor.TidyWriter.shortend_elements = [
+    'AREA','BASE','BASEFONT','BR','COL','FRAME','HR','IMG','INPUT',
+    'ISINDEX','LINK','','META','PARAM','EMBED','SOURCE','WBR','TRACK'
+];
+
+Roo.htmleditor.TidyWriter.whitespace_elements = [
+    'PRE','SCRIPT','NOSCRIPT','STYLE','TEXTAREA','VIDEO','AUDIO','IFRAME','OBJECT','CODE'
+];/***
+ * This is based loosely on tinymce 
+ * @class Roo.htmleditor.TidyEntities
+ * @static
+ * https://github.com/thorn0/tinymce.html/blob/master/tinymce.html.js
+ *
+ * Not 100% sure this is actually used or needed.
+ */
+
+Roo.htmleditor.TidyEntities = {
+    
+    /**
+     * initialize data..
+     */
+    init : function (){
+     
+        this.namedEntities = this.buildEntitiesLookup(this.namedEntitiesData, 32);
+       
+    },
+
+
+    buildEntitiesLookup: function(items, radix) {
+        var i, chr, entity, lookup = {};
+        if (!items) {
+            return {};
+        }
+        items = typeof(items) == 'string' ? items.split(',') : items;
+        radix = radix || 10;
+        // Build entities lookup table
+        for (i = 0; i < items.length; i += 2) {
+            chr = String.fromCharCode(parseInt(items[i], radix));
+            // Only add non base entities
+            if (!this.baseEntities[chr]) {
+                entity = '&' + items[i + 1] + ';';
+                lookup[chr] = entity;
+                lookup[entity] = chr;
+            }
+        }
+        return lookup;
+        
+    },
+    
+    asciiMap : {
+            128: '€',
+            130: '‚',
+            131: 'ƒ',
+            132: '„',
+            133: '…',
+            134: '†',
+            135: '‡',
+            136: 'ˆ',
+            137: '‰',
+            138: 'Š',
+            139: '‹',
+            140: 'Œ',
+            142: 'Ž',
+            145: '‘',
+            146: '’',
+            147: '“',
+            148: '”',
+            149: '•',
+            150: '–',
+            151: '—',
+            152: '˜',
+            153: '™',
+            154: 'š',
+            155: '›',
+            156: 'œ',
+            158: 'ž',
+            159: 'Ÿ'
+    },
+    // Raw entities
+    baseEntities : {
+        '"': '&quot;',
+        // Needs to be escaped since the YUI compressor would otherwise break the code
+        '\'': '&#39;',
+        '<': '&lt;',
+        '>': '&gt;',
+        '&': '&amp;',
+        '`': '&#96;'
+    },
+    // Reverse lookup table for raw entities
+    reverseEntities : {
+        '&lt;': '<',
+        '&gt;': '>',
+        '&amp;': '&',
+        '&quot;': '"',
+        '&apos;': '\''
+    },
+    
+    attrsCharsRegExp : /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
+    textCharsRegExp : /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
+    rawCharsRegExp : /[<>&\"\']/g,
+    entityRegExp : /&#([a-z0-9]+);?|&([a-z0-9]+);/gi,
+    namedEntities  : false,
+    namedEntitiesData : [ 
+        '50',
+        'nbsp',
+        '51',
+        'iexcl',
+        '52',
+        'cent',
+        '53',
+        'pound',
+        '54',
+        'curren',
+        '55',
+        'yen',
+        '56',
+        'brvbar',
+        '57',
+        'sect',
+        '58',
+        'uml',
+        '59',
+        'copy',
+        '5a',
+        'ordf',
+        '5b',
+        'laquo',
+        '5c',
+        'not',
+        '5d',
+        'shy',
+        '5e',
+        'reg',
+        '5f',
+        'macr',
+        '5g',
+        'deg',
+        '5h',
+        'plusmn',
+        '5i',
+        'sup2',
+        '5j',
+        'sup3',
+        '5k',
+        'acute',
+        '5l',
+        'micro',
+        '5m',
+        'para',
+        '5n',
+        'middot',
+        '5o',
+        'cedil',
+        '5p',
+        'sup1',
+        '5q',
+        'ordm',
+        '5r',
+        'raquo',
+        '5s',
+        'frac14',
+        '5t',
+        'frac12',
+        '5u',
+        'frac34',
+        '5v',
+        'iquest',
+        '60',
+        'Agrave',
+        '61',
+        'Aacute',
+        '62',
+        'Acirc',
+        '63',
+        'Atilde',
+        '64',
+        'Auml',
+        '65',
+        'Aring',
+        '66',
+        'AElig',
+        '67',
+        'Ccedil',
+        '68',
+        'Egrave',
+        '69',
+        'Eacute',
+        '6a',
+        'Ecirc',
+        '6b',
+        'Euml',
+        '6c',
+        'Igrave',
+        '6d',
+        'Iacute',
+        '6e',
+        'Icirc',
+        '6f',
+        'Iuml',
+        '6g',
+        'ETH',
+        '6h',
+        'Ntilde',
+        '6i',
+        'Ograve',
+        '6j',
+        'Oacute',
+        '6k',
+        'Ocirc',
+        '6l',
+        'Otilde',
+        '6m',
+        'Ouml',
+        '6n',
+        'times',
+        '6o',
+        'Oslash',
+        '6p',
+        'Ugrave',
+        '6q',
+        'Uacute',
+        '6r',
+        'Ucirc',
+        '6s',
+        'Uuml',
+        '6t',
+        'Yacute',
+        '6u',
+        'THORN',
+        '6v',
+        'szlig',
+        '70',
+        'agrave',
+        '71',
+        'aacute',
+        '72',
+        'acirc',
+        '73',
+        'atilde',
+        '74',
+        'auml',
+        '75',
+        'aring',
+        '76',
+        'aelig',
+        '77',
+        'ccedil',
+        '78',
+        'egrave',
+        '79',
+        'eacute',
+        '7a',
+        'ecirc',
+        '7b',
+        'euml',
+        '7c',
+        'igrave',
+        '7d',
+        'iacute',
+        '7e',
+        'icirc',
+        '7f',
+        'iuml',
+        '7g',
+        'eth',
+        '7h',
+        'ntilde',
+        '7i',
+        'ograve',
+        '7j',
+        'oacute',
+        '7k',
+        'ocirc',
+        '7l',
+        'otilde',
+        '7m',
+        'ouml',
+        '7n',
+        'divide',
+        '7o',
+        'oslash',
+        '7p',
+        'ugrave',
+        '7q',
+        'uacute',
+        '7r',
+        'ucirc',
+        '7s',
+        'uuml',
+        '7t',
+        'yacute',
+        '7u',
+        'thorn',
+        '7v',
+        'yuml',
+        'ci',
+        'fnof',
+        'sh',
+        'Alpha',
+        'si',
+        'Beta',
+        'sj',
+        'Gamma',
+        'sk',
+        'Delta',
+        'sl',
+        'Epsilon',
+        'sm',
+        'Zeta',
+        'sn',
+        'Eta',
+        'so',
+        'Theta',
+        'sp',
+        'Iota',
+        'sq',
+        'Kappa',
+        'sr',
+        'Lambda',
+        'ss',
+        'Mu',
+        'st',
+        'Nu',
+        'su',
+        'Xi',
+        'sv',
+        'Omicron',
+        't0',
+        'Pi',
+        't1',
+        'Rho',
+        't3',
+        'Sigma',
+        't4',
+        'Tau',
+        't5',
+        'Upsilon',
+        't6',
+        'Phi',
+        't7',
+        'Chi',
+        't8',
+        'Psi',
+        't9',
+        'Omega',
+        'th',
+        'alpha',
+        'ti',
+        'beta',
+        'tj',
+        'gamma',
+        'tk',
+        'delta',
+        'tl',
+        'epsilon',
+        'tm',
+        'zeta',
+        'tn',
+        'eta',
+        'to',
+        'theta',
+        'tp',
+        'iota',
+        'tq',
+        'kappa',
+        'tr',
+        'lambda',
+        'ts',
+        'mu',
+        'tt',
+        'nu',
+        'tu',
+        'xi',
+        'tv',
+        'omicron',
+        'u0',
+        'pi',
+        'u1',
+        'rho',
+        'u2',
+        'sigmaf',
+        'u3',
+        'sigma',
+        'u4',
+        'tau',
+        'u5',
+        'upsilon',
+        'u6',
+        'phi',
+        'u7',
+        'chi',
+        'u8',
+        'psi',
+        'u9',
+        'omega',
+        'uh',
+        'thetasym',
+        'ui',
+        'upsih',
+        'um',
+        'piv',
+        '812',
+        'bull',
+        '816',
+        'hellip',
+        '81i',
+        'prime',
+        '81j',
+        'Prime',
+        '81u',
+        'oline',
+        '824',
+        'frasl',
+        '88o',
+        'weierp',
+        '88h',
+        'image',
+        '88s',
+        'real',
+        '892',
+        'trade',
+        '89l',
+        'alefsym',
+        '8cg',
+        'larr',
+        '8ch',
+        'uarr',
+        '8ci',
+        'rarr',
+        '8cj',
+        'darr',
+        '8ck',
+        'harr',
+        '8dl',
+        'crarr',
+        '8eg',
+        'lArr',
+        '8eh',
+        'uArr',
+        '8ei',
+        'rArr',
+        '8ej',
+        'dArr',
+        '8ek',
+        'hArr',
+        '8g0',
+        'forall',
+        '8g2',
+        'part',
+        '8g3',
+        'exist',
+        '8g5',
+        'empty',
+        '8g7',
+        'nabla',
+        '8g8',
+        'isin',
+        '8g9',
+        'notin',
+        '8gb',
+        'ni',
+        '8gf',
+        'prod',
+        '8gh',
+        'sum',
+        '8gi',
+        'minus',
+        '8gn',
+        'lowast',
+        '8gq',
+        'radic',
+        '8gt',
+        'prop',
+        '8gu',
+        'infin',
+        '8h0',
+        'ang',
+        '8h7',
+        'and',
+        '8h8',
+        'or',
+        '8h9',
+        'cap',
+        '8ha',
+        'cup',
+        '8hb',
+        'int',
+        '8hk',
+        'there4',
+        '8hs',
+        'sim',
+        '8i5',
+        'cong',
+        '8i8',
+        'asymp',
+        '8j0',
+        'ne',
+        '8j1',
+        'equiv',
+        '8j4',
+        'le',
+        '8j5',
+        'ge',
+        '8k2',
+        'sub',
+        '8k3',
+        'sup',
+        '8k4',
+        'nsub',
+        '8k6',
+        'sube',
+        '8k7',
+        'supe',
+        '8kl',
+        'oplus',
+        '8kn',
+        'otimes',
+        '8l5',
+        'perp',
+        '8m5',
+        'sdot',
+        '8o8',
+        'lceil',
+        '8o9',
+        'rceil',
+        '8oa',
+        'lfloor',
+        '8ob',
+        'rfloor',
+        '8p9',
+        'lang',
+        '8pa',
+        'rang',
+        '9ea',
+        'loz',
+        '9j0',
+        'spades',
+        '9j3',
+        'clubs',
+        '9j5',
+        'hearts',
+        '9j6',
+        'diams',
+        'ai',
+        'OElig',
+        'aj',
+        'oelig',
+        'b0',
+        'Scaron',
+        'b1',
+        'scaron',
+        'bo',
+        'Yuml',
+        'm6',
+        'circ',
+        'ms',
+        'tilde',
+        '802',
+        'ensp',
+        '803',
+        'emsp',
+        '809',
+        'thinsp',
+        '80c',
+        'zwnj',
+        '80d',
+        'zwj',
+        '80e',
+        'lrm',
+        '80f',
+        'rlm',
+        '80j',
+        'ndash',
+        '80k',
+        'mdash',
+        '80o',
+        'lsquo',
+        '80p',
+        'rsquo',
+        '80q',
+        'sbquo',
+        '80s',
+        'ldquo',
+        '80t',
+        'rdquo',
+        '80u',
+        'bdquo',
+        '810',
+        'dagger',
+        '811',
+        'Dagger',
+        '81g',
+        'permil',
+        '81p',
+        'lsaquo',
+        '81q',
+        'rsaquo',
+        '85c',
+        'euro'
+    ],
+
+         
+    /**
+     * Encodes the specified string using raw entities. This means only the required XML base entities will be encoded.
+     *
+     * @method encodeRaw
+     * @param {String} text Text to encode.
+     * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
+     * @return {String} Entity encoded text.
+     */
+    encodeRaw: function(text, attr)
+    {
+        var t = this;
+        return text.replace(attr ? this.attrsCharsRegExp : this.textCharsRegExp, function(chr) {
+            return t.baseEntities[chr] || chr;
+        });
+    },
+    /**
+     * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents
+     * since it doesn't know if the context is within a attribute or text node. This was added for compatibility
+     * and is exposed as the DOMUtils.encode function.
+     *
+     * @method encodeAllRaw
+     * @param {String} text Text to encode.
+     * @return {String} Entity encoded text.
+     */
+    encodeAllRaw: function(text) {
+        var t = this;
+        return ('' + text).replace(this.rawCharsRegExp, function(chr) {
+            return t.baseEntities[chr] || chr;
+        });
+    },
+    /**
+     * Encodes the specified string using numeric entities. The core entities will be
+     * encoded as named ones but all non lower ascii characters will be encoded into numeric entities.
+     *
+     * @method encodeNumeric
+     * @param {String} text Text to encode.
+     * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
+     * @return {String} Entity encoded text.
+     */
+    encodeNumeric: function(text, attr) {
+        var t = this;
+        return text.replace(attr ? this.attrsCharsRegExp : this.textCharsRegExp, function(chr) {
+            // Multi byte sequence convert it to a single entity
+            if (chr.length > 1) {
+                return '&#' + (1024 * (chr.charCodeAt(0) - 55296) + (chr.charCodeAt(1) - 56320) + 65536) + ';';
+            }
+            return t.baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
+        });
+    },
+    /**
+     * Encodes the specified string using named entities. The core entities will be encoded
+     * as named ones but all non lower ascii characters will be encoded into named entities.
+     *
+     * @method encodeNamed
+     * @param {String} text Text to encode.
+     * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
+     * @param {Object} entities Optional parameter with entities to use.
+     * @return {String} Entity encoded text.
+     */
+    encodeNamed: function(text, attr, entities) {
+        var t = this;
+        entities = entities || this.namedEntities;
+        return text.replace(attr ? this.attrsCharsRegExp : this.textCharsRegExp, function(chr) {
+            return t.baseEntities[chr] || entities[chr] || chr;
+        });
+    },
+    /**
+     * Returns an encode function based on the name(s) and it's optional entities.
+     *
+     * @method getEncodeFunc
+     * @param {String} name Comma separated list of encoders for example named,numeric.
+     * @param {String} entities Optional parameter with entities to use instead of the built in set.
+     * @return {function} Encode function to be used.
+     */
+    getEncodeFunc: function(name, entities) {
+        entities = this.buildEntitiesLookup(entities) || this.namedEntities;
+        var t = this;
+        function encodeNamedAndNumeric(text, attr) {
+            return text.replace(attr ? t.attrsCharsRegExp : t.textCharsRegExp, function(chr) {
+                return t.baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
+            });
+        }
+
+        function encodeCustomNamed(text, attr) {
+            return t.encodeNamed(text, attr, entities);
+        }
+        // Replace + with , to be compatible with previous TinyMCE versions
+        name = this.makeMap(name.replace(/\+/g, ','));
+        // Named and numeric encoder
+        if (name.named && name.numeric) {
+            return this.encodeNamedAndNumeric;
+        }
+        // Named encoder
+        if (name.named) {
+            // Custom names
+            if (entities) {
+                return encodeCustomNamed;
+            }
+            return this.encodeNamed;
+        }
+        // Numeric
+        if (name.numeric) {
+            return this.encodeNumeric;
+        }
+        // Raw encoder
+        return this.encodeRaw;
+    },
+    /**
+     * Decodes the specified string, this will replace entities with raw UTF characters.
+     *
+     * @method decode
+     * @param {String} text Text to entity decode.
+     * @return {String} Entity decoded string.
+     */
+    decode: function(text)
+    {
+        var  t = this;
+        return text.replace(this.entityRegExp, function(all, numeric) {
+            if (numeric) {
+                numeric = 'x' === numeric.charAt(0).toLowerCase() ? parseInt(numeric.substr(1), 16) : parseInt(numeric, 10);
+                // Support upper UTF
+                if (numeric > 65535) {
+                    numeric -= 65536;
+                    return String.fromCharCode(55296 + (numeric >> 10), 56320 + (1023 & numeric));
+                }
+                return t.asciiMap[numeric] || String.fromCharCode(numeric);
+            }
+            return t.reverseEntities[all] || t.namedEntities[all] || t.nativeDecode(all);
+        });
+    },
+    nativeDecode : function (text) {
+        return text;
+    },
+    makeMap : function (items, delim, map) {
+               var i;
+               items = items || [];
+               delim = delim || ',';
+               if (typeof items == "string") {
+                       items = items.split(delim);
+               }
+               map = map || {};
+               i = items.length;
+               while (i--) {
+                       map[items[i]] = {};
+               }
+               return map;
+       }
+};
+    
+    
+    
+Roo.htmleditor.TidyEntities.init();
+/**
+ * @class Roo.htmleditor.KeyEnter
+ * Handle Enter press..
+ * @cfg {Roo.HtmlEditorCore} core the editor.
+ * @constructor
+ * Create a new Filter.
+ * @param {Object} config Configuration options
+ */
+
+
+
+
+
+Roo.htmleditor.KeyEnter = function(cfg) {
+    Roo.apply(this, cfg);
+    // this does not actually call walk as it's really just a abstract class
+    Roo.get(this.core.doc.body).on('keypress', this.keypress, this);
+}
+
+//Roo.htmleditor.KeyEnter.i = 0;
+
+
+Roo.htmleditor.KeyEnter.prototype = {
+    
+    core : false,
+    
+    keypress : function(e)
+    {
+        if (e.charCode != 13 && e.charCode != 10) {
+            Roo.log([e.charCode,e]);
+            return true;
+        }
+        e.preventDefault();
+        // https://stackoverflow.com/questions/18552336/prevent-contenteditable-adding-div-on-enter-chrome
+        var doc = this.core.doc;
+          //add a new line
+       
+    
+        var sel = this.core.getSelection();
+        var range = sel.getRangeAt(0);
+        var n = range.commonAncestorContainer;
+        var pc = range.closest([ 'ol', 'ul']);
+        var pli = range.closest('li');
+        if (!pc || e.ctrlKey) {
+            // on it list, or ctrl pressed.
+            if (!e.ctrlKey) {
+                sel.insertNode('br', 'after'); 
+            } else {
+                // only do this if we have ctrl key..
+                var br = doc.createElement('br');
+                br.className = 'clear';
+                br.setAttribute('style', 'clear: both');
+                sel.insertNode(br, 'after'); 
+            }
+            
+         
+            this.core.undoManager.addEvent();
+            this.core.fireEditorEvent(e);
+            return false;
+        }
+        
+        // deal with <li> insetion
+        if (pli.innerText.trim() == '' &&
+            pli.previousSibling &&
+            pli.previousSibling.nodeName == 'LI' &&
+            pli.previousSibling.innerText.trim() ==  '') {
+            pli.parentNode.removeChild(pli.previousSibling);
+            sel.cursorAfter(pc);
+            this.core.undoManager.addEvent();
+            this.core.fireEditorEvent(e);
+            return false;
+        }
+    
+        var li = doc.createElement('LI');
+        li.innerHTML = '&nbsp;';
+        if (!pli || !pli.firstSibling) {
+            pc.appendChild(li);
+        } else {
+            pli.parentNode.insertBefore(li, pli.firstSibling);
+        }
+        sel.cursorText (li.firstChild);
+      
+        this.core.undoManager.addEvent();
+        this.core.fireEditorEvent(e);
+
+        return false;
+        
+    
+        
+        
+         
+    }
+};
+     
+/**
+ * @class Roo.htmleditor.Block
+ * Base class for html editor blocks - do not use it directly .. extend it..
+ * @cfg {DomElement} node The node to apply stuff to.
+ * @cfg {String} friendly_name the name that appears in the context bar about this block
+ * @cfg {Object} Context menu - see Roo.form.HtmlEditor.ToolbarContext
+ * @constructor
+ * Create a new Filter.
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.Block  = function(cfg)
+{
+    // do nothing .. should not be called really.
+}
+/**
+ * factory method to get the block from an element (using cache if necessary)
+ * @static
+ * @param {HtmlElement} the dom element
+ */
+Roo.htmleditor.Block.factory = function(node)
+{
+    var cc = Roo.htmleditor.Block.cache;
+    var id = Roo.get(node).id;
+    if (typeof(cc[id]) != 'undefined' && (!cc[id].node || cc[id].node.closest('body'))) {
+        Roo.htmleditor.Block.cache[id].readElement(node);
+        return Roo.htmleditor.Block.cache[id];
+    }
+    var db  = node.getAttribute('data-block');
+    if (!db) {
+        db = node.nodeName.toLowerCase().toUpperCaseFirst();
+    }
+    var cls = Roo.htmleditor['Block' + db];
+    if (typeof(cls) == 'undefined') {
+        //Roo.log(node.getAttribute('data-block'));
+        Roo.log("OOps missing block : " + 'Block' + db);
+        return false;
+    }
+    Roo.htmleditor.Block.cache[id] = new cls({ node: node });
+    return Roo.htmleditor.Block.cache[id];  /// should trigger update element
+};
+
+/**
+ * initalize all Elements from content that are 'blockable'
+ * @static
+ * @param the body element
+ */
+Roo.htmleditor.Block.initAll = function(body, type)
+{
+    if (typeof(type) == 'undefined') {
+        var ia = Roo.htmleditor.Block.initAll;
+        ia(body,'table');
+        ia(body,'td');
+        ia(body,'figure');
+        return;
+    }
+    Roo.each(Roo.get(body).query(type), function(e) {
+        Roo.htmleditor.Block.factory(e);    
+    },this);
+};
+// question goes here... do we need to clear out this cache sometimes?
+// or show we make it relivant to the htmleditor.
+Roo.htmleditor.Block.cache = {};
+
+Roo.htmleditor.Block.prototype = {
+    
+    node : false,
+    
+     // used by context menu
+    friendly_name : 'Based Block',
+    
+    // text for button to delete this element
+    deleteTitle : false,
+    
+    context : false,
+    /**
+     * Update a node with values from this object
+     * @param {DomElement} node
+     */
+    updateElement : function(node)
+    {
+        Roo.DomHelper.update(node === undefined ? this.node : node, this.toObject());
+    },
+     /**
+     * convert to plain HTML for calling insertAtCursor..
+     */
+    toHTML : function()
+    {
+        return Roo.DomHelper.markup(this.toObject());
+    },
+    /**
+     * used by readEleemnt to extract data from a node
+     * may need improving as it's pretty basic
+     
+     * @param {DomElement} node
+     * @param {String} tag - tag to find, eg. IMG ?? might be better to use DomQuery ?
+     * @param {String} attribute (use html - for contents, style for using next param as style, or false to return the node)
+     * @param {String} style the style property - eg. text-align
+     */
+    getVal : function(node, tag, attr, style)
+    {
+        var n = node;
+        if (tag !== true && n.tagName != tag.toUpperCase()) {
+            // in theory we could do figure[3] << 3rd figure? or some more complex search..?
+            // but kiss for now.
+            n = node.getElementsByTagName(tag).item(0);
+        }
+        if (!n) {
+            return '';
+        }
+        if (attr === false) {
+            return n;
+        }
+        if (attr == 'html') {
+            return n.innerHTML;
+        }
+        if (attr == 'style') {
+            return n.style[style]; 
+        }
+        
+        return n.hasAttribute(attr) ? n.getAttribute(attr) : '';
+            
+    },
+    /**
+     * create a DomHelper friendly object - for use with 
+     * Roo.DomHelper.markup / overwrite / etc..
+     * (override this)
+     */
+    toObject : function()
+    {
+        return {};
+    },
+      /**
+     * Read a node that has a 'data-block' property - and extract the values from it.
+     * @param {DomElement} node - the node
+     */
+    readElement : function(node)
+    {
+        
+    } 
+    
+    
+};
+
+
+/**
+ * @class Roo.htmleditor.BlockFigure
+ * Block that has an image and a figcaption
+ * @cfg {String} image_src the url for the image
+ * @cfg {String} align (left|right) alignment for the block default left
+ * @cfg {String} caption the text to appear below  (and in the alt tag)
+ * @cfg {String} caption_display (block|none) display or not the caption
+ * @cfg {String|number} image_width the width of the image number or %?
+ * @cfg {String|number} image_height the height of the image number or %?
+ * 
+ * @constructor
+ * Create a new Filter.
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.BlockFigure = function(cfg)
+{
+    if (cfg.node) {
+        this.readElement(cfg.node);
+        this.updateElement(cfg.node);
+    }
+    Roo.apply(this, cfg);
+}
+Roo.extend(Roo.htmleditor.BlockFigure, Roo.htmleditor.Block, {
+    
+    // setable values.
+    image_src: '',
+    align: 'center',
+    caption : '',
+    caption_display : 'block',
+    width : '100%',
+    cls : '',
+    href: '',
+    video_url : '',
+    
+    // margin: '2%', not used
+    
+    text_align: 'left', //   (left|right) alignment for the text caption default left. - not used at present
+
+    
+    // used by context menu
+    friendly_name : 'Image with caption',
+    deleteTitle : "Delete Image and Caption",
+    
+    contextMenu : function(toolbar)
+    {
+        
+        var block = function() {
+            return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode);
+        };
+        
+        
+        var rooui =  typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap;
+        
+        var syncValue = toolbar.editorcore.syncValue;
+        
+        var fields = {};
+        
+        return [
+             {
+                xtype : 'TextItem',
+                text : "Source: ",
+                xns : rooui.Toolbar  //Boostrap?
+            },
+            {
+                xtype : 'Button',
+                text: 'Change Image URL',
+                 
+                listeners : {
+                    click: function (btn, state)
+                    {
+                        var b = block();
+                        
+                        Roo.MessageBox.show({
+                            title : "Image Source URL",
+                            msg : "Enter the url for the image",
+                            buttons: Roo.MessageBox.OKCANCEL,
+                            fn: function(btn, val){
+                                if (btn != 'ok') {
+                                    return;
+                                }
+                                b.image_src = val;
+                                b.updateElement();
+                                syncValue();
+                                toolbar.editorcore.onEditorEvent();
+                            },
+                            minWidth:250,
+                            prompt:true,
+                            //multiline: multiline,
+                            modal : true,
+                            value : b.image_src
+                        });
+                    }
+                },
+                xns : rooui.Toolbar
+            },
+         
+            {
+                xtype : 'Button',
+                text: 'Change Link URL',
+                 
+                listeners : {
+                    click: function (btn, state)
+                    {
+                        var b = block();
+                        
+                        Roo.MessageBox.show({
+                            title : "Link URL",
+                            msg : "Enter the url for the link - leave blank to have no link",
+                            buttons: Roo.MessageBox.OKCANCEL,
+                            fn: function(btn, val){
+                                if (btn != 'ok') {
+                                    return;
+                                }
+                                b.href = val;
+                                b.updateElement();
+                                syncValue();
+                                toolbar.editorcore.onEditorEvent();
+                            },
+                            minWidth:250,
+                            prompt:true,
+                            //multiline: multiline,
+                            modal : true,
+                            value : b.href
+                        });
+                    }
+                },
+                xns : rooui.Toolbar
+            },
+            {
+                xtype : 'Button',
+                text: 'Show Video URL',
+                 
+                listeners : {
+                    click: function (btn, state)
+                    {
+                        Roo.MessageBox.alert("Video URL",
+                            block().video_url == '' ? 'This image is not linked ot a video' :
+                                'The image is linked to: <a target="_new" href="' + block().video_url + '">' + block().video_url + '</a>');
+                    }
+                },
+                xns : rooui.Toolbar
+            },
+            
+            
+            {
+                xtype : 'TextItem',
+                text : "Width: ",
+                xns : rooui.Toolbar  //Boostrap?
+            },
+            {
+                xtype : 'ComboBox',
+                allowBlank : false,
+                displayField : 'val',
+                editable : true,
+                listWidth : 100,
+                triggerAction : 'all',
+                typeAhead : true,
+                valueField : 'val',
+                width : 70,
+                name : 'width',
+                listeners : {
+                    select : function (combo, r, index)
+                    {
+                        toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+                        var b = block();
+                        b.width = r.get('val');
+                        b.updateElement();
+                        syncValue();
+                        toolbar.editorcore.onEditorEvent();
+                    }
+                },
+                xns : rooui.form,
+                store : {
+                    xtype : 'SimpleStore',
+                    data : [
+                        ['100%'],
+                        ['80%'],
+                        ['50%'],
+                        ['20%'],
+                        ['10%']
+                    ],
+                    fields : [ 'val'],
+                    xns : Roo.data
+                }
+            },
+            {
+                xtype : 'TextItem',
+                text : "Align: ",
+                xns : rooui.Toolbar  //Boostrap?
+            },
+            {
+                xtype : 'ComboBox',
+                allowBlank : false,
+                displayField : 'val',
+                editable : true,
+                listWidth : 100,
+                triggerAction : 'all',
+                typeAhead : true,
+                valueField : 'val',
+                width : 70,
+                name : 'align',
+                listeners : {
+                    select : function (combo, r, index)
+                    {
+                        toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+                        var b = block();
+                        b.align = r.get('val');
+                        b.updateElement();
+                        syncValue();
+                        toolbar.editorcore.onEditorEvent();
+                    }
+                },
+                xns : rooui.form,
+                store : {
+                    xtype : 'SimpleStore',
+                    data : [
+                        ['left'],
+                        ['right'],
+                        ['center']
+                    ],
+                    fields : [ 'val'],
+                    xns : Roo.data
+                }
+            },
+            
+             
+            {
+                xtype : 'Button',
+                text: 'Hide Caption',
+                name : 'caption_display',
+                pressed : false,
+                enableToggle : true,
+                setValue : function(v) {
+                    // this trigger toggle.
+                     
+                    this.setText(v ? "Hide Caption" : "Show Caption");
+                    this.setPressed(v != 'block');
+                },
+                listeners : {
+                    toggle: function (btn, state)
+                    {
+                        var b  = block();
+                        b.caption_display = b.caption_display == 'block' ? 'none' : 'block';
+                        this.setText(b.caption_display == 'block' ? "Hide Caption" : "Show Caption");
+                        b.updateElement();
+                        syncValue();
+                        toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+                        toolbar.editorcore.onEditorEvent();
+                    }
+                },
+                xns : rooui.Toolbar
+            }
+        ];
+        
+    },
+    /**
+     * create a DomHelper friendly object - for use with
+     * Roo.DomHelper.markup / overwrite / etc..
+     */
+    toObject : function()
+    {
+        var d = document.createElement('div');
+        d.innerHTML = this.caption;
+        
+        var m = this.width != '100%' && this.align == 'center' ? '0 auto' : 0; 
+        
+        var iw = this.align == 'center' ? this.width : '100%';
+        var img =   {
+            tag : 'img',
+            contenteditable : 'false',
+            src : this.image_src,
+            alt : d.innerText.replace(/\n/g, " ").replace(/\s+/g, ' ').trim(), // removeHTML and reduce spaces..
+            style: {
+                width : iw,
+                maxWidth : iw + ' !important', // this is not getting rendered?
+                margin : m  
+                
+            }
+        };
+        /*
+        '<div class="{0}" width="420" height="315" src="{1}" frameborder="0" allowfullscreen>' +
+                    '<a href="{2}">' + 
+                        '<img class="{0}-thumbnail" src="{3}/Images/{4}/{5}#image-{4}" />' + 
+                    '</a>' + 
+                '</div>',
+        */
+                
+        if (this.href.length > 0) {
+            img = {
+                tag : 'a',
+                href: this.href,
+                contenteditable : 'true',
+                cn : [
+                    img
+                ]
+            };
+        }
+        
+        
+        if (this.video_url.length > 0) {
+            img = {
+                tag : 'div',
+                cls : this.cls,
+                frameborder : 0,
+                allowfullscreen : true,
+                width : 420,  // these are for video tricks - that we replace the outer
+                height : 315,
+                src : this.video_url,
+                cn : [
+                    img
+                ]
+            };
+        }
+        // we remove caption totally if its hidden... - will delete data.. but otherwise we end up with fake caption
+        var captionhtml = this.caption_display == 'none' ? '' : (this.caption.length ? this.caption : "Caption");
+        
+  
+        var ret =   {
+            tag: 'figure',
+            'data-block' : 'Figure',
+            'data-width' : this.width, 
+            contenteditable : 'false',
+            
+            style : {
+                display: 'block',
+                float :  this.align ,
+                maxWidth :  this.align == 'center' ? '100% !important' : (this.width + ' !important'),
+                width : this.align == 'center' ? '100%' : this.width,
+                margin:  '0px',
+                padding: this.align == 'center' ? '0' : '0 10px' ,
+                textAlign : this.align   // seems to work for email..
+                
+            },
+           
+            
+            align : this.align,
+            cn : [
+                img,
+              
+                {
+                    tag: 'figcaption',
+                    'data-display' : this.caption_display,
+                    style : {
+                        textAlign : 'left',
+                        fontSize : '16px',
+                        lineHeight : '24px',
+                        display : this.caption_display,
+                        maxWidth : (this.align == 'center' ?  this.width : '100%' ) + ' !important',
+                        margin: m,
+                        width: this.align == 'center' ?  this.width : '100%' 
+                    
+                         
+                    },
+                    cls : this.cls.length > 0 ? (this.cls  + '-thumbnail' ) : '',
+                    cn : [
+                        {
+                            tag: 'div',
+                            style  : {
+                                marginTop : '16px',
+                                textAlign : 'left'
+                            },
+                            align: 'left',
+                            cn : [
+                                {
+                                    // we can not rely on yahoo syndication to use CSS elements - so have to use  '<i>' to encase stuff.
+                                    tag : 'i',
+                                    contenteditable : true,
+                                    html : captionhtml
+                                }
+                                
+                            ]
+                        }
+                        
+                    ]
+                    
+                }
+            ]
+        };
+        return ret;
+         
+    },
+    
+    readElement : function(node)
+    {
+        // this should not really come from the link...
+        this.video_url = this.getVal(node, 'div', 'src');
+        this.cls = this.getVal(node, 'div', 'class');
+        this.href = this.getVal(node, 'a', 'href');
+        
+        
+        this.image_src = this.getVal(node, 'img', 'src');
+         
+        this.align = this.getVal(node, 'figure', 'align');
+        var figcaption = this.getVal(node, 'figcaption', false);
+        if (figcaption !== '') {
+            this.caption = this.getVal(figcaption, 'i', 'html');
+        }
+        
+
+        this.caption_display = this.getVal(node, 'figcaption', 'data-display');
+        //this.text_align = this.getVal(node, 'figcaption', 'style','text-align');
+        this.width = this.getVal(node, true, 'data-width');
+        //this.margin = this.getVal(node, 'figure', 'style', 'margin');
+        
+    },
+    removeNode : function()
+    {
+        return this.node;
+    }
+    
+  
+   
+     
+    
+    
+    
+    
+})
+
+
+/**
+ * @class Roo.htmleditor.BlockTable
+ * Block that manages a table
+ * 
+ * @constructor
+ * Create a new Filter.
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.BlockTable = function(cfg)
+{
+    if (cfg.node) {
+        this.readElement(cfg.node);
+        this.updateElement(cfg.node);
+    }
+    Roo.apply(this, cfg);
+    if (!cfg.node) {
+        this.rows = [];
+        for(var r = 0; r < this.no_row; r++) {
+            this.rows[r] = [];
+            for(var c = 0; c < this.no_col; c++) {
+                this.rows[r][c] = this.emptyCell();
+            }
+        }
+    }
+    
+    
+}
+Roo.extend(Roo.htmleditor.BlockTable, Roo.htmleditor.Block, {
+    rows : false,
+    no_col : 1,
+    no_row : 1,
+    
+    
+    width: '100%',
+    
+    // used by context menu
+    friendly_name : 'Table',
+    deleteTitle : 'Delete Table',
+    // context menu is drawn once..
+    
+    contextMenu : function(toolbar)
+    {
+        
+        var block = function() {
+            return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode);
+        };
+        
+        
+        var rooui =  typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap;
+        
+        var syncValue = toolbar.editorcore.syncValue;
+        
+        var fields = {};
+        
+        return [
+            {
+                xtype : 'TextItem',
+                text : "Width: ",
+                xns : rooui.Toolbar  //Boostrap?
+            },
+            {
+                xtype : 'ComboBox',
+                allowBlank : false,
+                displayField : 'val',
+                editable : true,
+                listWidth : 100,
+                triggerAction : 'all',
+                typeAhead : true,
+                valueField : 'val',
+                width : 100,
+                name : 'width',
+                listeners : {
+                    select : function (combo, r, index)
+                    {
+                        toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+                        var b = block();
+                        b.width = r.get('val');
+                        b.updateElement();
+                        syncValue();
+                        toolbar.editorcore.onEditorEvent();
+                    }
+                },
+                xns : rooui.form,
+                store : {
+                    xtype : 'SimpleStore',
+                    data : [
+                        ['100%'],
+                        ['auto']
+                    ],
+                    fields : [ 'val'],
+                    xns : Roo.data
+                }
+            },
+            // -------- Cols
+            
+            {
+                xtype : 'TextItem',
+                text : "Columns: ",
+                xns : rooui.Toolbar  //Boostrap?
+            },
+         
+            {
+                xtype : 'Button',
+                text: '-',
+                listeners : {
+                    click : function (_self, e)
+                    {
+                        toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+                        block().removeColumn();
+                        syncValue();
+                        toolbar.editorcore.onEditorEvent();
+                    }
+                },
+                xns : rooui.Toolbar
+            },
+            {
+                xtype : 'Button',
+                text: '+',
+                listeners : {
+                    click : function (_self, e)
+                    {
+                        toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+                        block().addColumn();
+                        syncValue();
+                        toolbar.editorcore.onEditorEvent();
+                    }
+                },
+                xns : rooui.Toolbar
+            },
+            // -------- ROWS
+            {
+                xtype : 'TextItem',
+                text : "Rows: ",
+                xns : rooui.Toolbar  //Boostrap?
+            },
+         
+            {
+                xtype : 'Button',
+                text: '-',
+                listeners : {
+                    click : function (_self, e)
+                    {
+                        toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+                        block().removeRow();
+                        syncValue();
+                        toolbar.editorcore.onEditorEvent();
+                    }
+                },
+                xns : rooui.Toolbar
+            },
+            {
+                xtype : 'Button',
+                text: '+',
+                listeners : {
+                    click : function (_self, e)
+                    {
+                        block().addRow();
+                        syncValue();
+                        toolbar.editorcore.onEditorEvent();
+                    }
+                },
+                xns : rooui.Toolbar
+            },
+            // -------- ROWS
+            {
+                xtype : 'Button',
+                text: 'Reset Column Widths',
+                listeners : {
+                    
+                    click : function (_self, e)
+                    {
+                        block().resetWidths();
+                        syncValue();
+                        toolbar.editorcore.onEditorEvent();
+                    }
+                },
+                xns : rooui.Toolbar
+            } 
+            
+            
+            
+        ];
+        
+    },
+    
+    
+  /**
+     * create a DomHelper friendly object - for use with
+     * Roo.DomHelper.markup / overwrite / etc..
+     * ?? should it be called with option to hide all editing features?
+     */
+    toObject : function()
+    {
+        
+        var ret = {
+            tag : 'table',
+            contenteditable : 'false', // this stops cell selection from picking the table.
+            'data-block' : 'Table',
+            style : {
+                width:  this.width,
+                border : 'solid 1px #000', // ??? hard coded?
+                'border-collapse' : 'collapse' 
+            },
+            cn : [
+                { tag : 'tbody' , cn : [] }
+            ]
+        };
+        
+        // do we have a head = not really 
+        var ncols = 0;
+        Roo.each(this.rows, function( row ) {
+            var tr = {
+                tag: 'tr',
+                style : {
+                    margin: '6px',
+                    border : 'solid 1px #000',
+                    textAlign : 'left' 
+                },
+                cn : [ ]
+            };
+            
+            ret.cn[0].cn.push(tr);
+            // does the row have any properties? ?? height?
+            var nc = 0;
+            Roo.each(row, function( cell ) {
+                
+                var td = {
+                    tag : 'td',
+                    contenteditable :  'true',
+                    'data-block' : 'Td',
+                    html : cell.html,
+                    style : cell.style
+                };
+                if (cell.colspan > 1) {
+                    td.colspan = cell.colspan ;
+                    nc += cell.colspan;
+                } else {
+                    nc++;
+                }
+                if (cell.rowspan > 1) {
+                    td.rowspan = cell.rowspan ;
+                }
+                
+                
+                // widths ?
+                tr.cn.push(td);
+                    
+                
+            }, this);
+            ncols = Math.max(nc, ncols);
+            
+            
+        }, this);
+        // add the header row..
+        
+        ncols++;
+         
+        
+        return ret;
+         
+    },
+    
+    readElement : function(node)
+    {
+        node  = node ? node : this.node ;
+        this.width = this.getVal(node, true, 'style', 'width') || '100%';
+        
+        this.rows = [];
+        this.no_row = 0;
+        var trs = Array.from(node.rows);
+        trs.forEach(function(tr) {
+            var row =  [];
+            this.rows.push(row);
+            
+            this.no_row++;
+            var no_column = 0;
+            Array.from(tr.cells).forEach(function(td) {
+                
+                var add = {
+                    colspan : td.hasAttribute('colspan') ? td.getAttribute('colspan')*1 : 1,
+                    rowspan : td.hasAttribute('rowspan') ? td.getAttribute('rowspan')*1 : 1,
+                    style : td.hasAttribute('style') ? td.getAttribute('style') : '',
+                    html : td.innerHTML
+                };
+                no_column += add.colspan;
+                     
+                
+                row.push(add);
+                
+                
+            },this);
+            this.no_col = Math.max(this.no_col, no_column);
+            
+            
+        },this);
+        
+        
+    },
+    normalizeRows: function()
+    {
+        var ret= [];
+        var rid = -1;
+        this.rows.forEach(function(row) {
+            rid++;
+            ret[rid] = [];
+            row = this.normalizeRow(row);
+            var cid = 0;
+            row.forEach(function(c) {
+                while (typeof(ret[rid][cid]) != 'undefined') {
+                    cid++;
+                }
+                if (typeof(ret[rid]) == 'undefined') {
+                    ret[rid] = [];
+                }
+                ret[rid][cid] = c;
+                c.row = rid;
+                c.col = cid;
+                if (c.rowspan < 2) {
+                    return;
+                }
+                
+                for(var i = 1 ;i < c.rowspan; i++) {
+                    if (typeof(ret[rid+i]) == 'undefined') {
+                        ret[rid+i] = [];
+                    }
+                    ret[rid+i][cid] = c;
+                }
+            });
+        }, this);
+        return ret;
+    
+    },
+    
+    normalizeRow: function(row)
+    {
+        var ret= [];
+        row.forEach(function(c) {
+            if (c.colspan < 2) {
+                ret.push(c);
+                return;
+            }
+            for(var i =0 ;i < c.colspan; i++) {
+                ret.push(c);
+            }
+        });
+        return ret;
+    
+    },
+    
+    deleteColumn : function(sel)
+    {
+        if (!sel || sel.type != 'col') {
+            return;
+        }
+        if (this.no_col < 2) {
+            return;
+        }
+        
+        this.rows.forEach(function(row) {
+            var cols = this.normalizeRow(row);
+            var col = cols[sel.col];
+            if (col.colspan > 1) {
+                col.colspan --;
+            } else {
+                row.remove(col);
+            }
+            
+        }, this);
+        this.no_col--;
+        
+    },
+    removeColumn : function()
+    {
+        this.deleteColumn({
+            type: 'col',
+            col : this.no_col-1
+        });
+        this.updateElement();
+    },
+    
+     
+    addColumn : function()
+    {
+        
+        this.rows.forEach(function(row) {
+            row.push(this.emptyCell());
+           
+        }, this);
+        this.updateElement();
+    },
+    
+    deleteRow : function(sel)
+    {
+        if (!sel || sel.type != 'row') {
+            return;
+        }
+        
+        if (this.no_row < 2) {
+            return;
+        }
+        
+        var rows = this.normalizeRows();
+        
+        
+        rows[sel.row].forEach(function(col) {
+            if (col.rowspan > 1) {
+                col.rowspan--;
+            } else {
+                col.remove = 1; // flage it as removed.
+            }
+            
+        }, this);
+        var newrows = [];
+        this.rows.forEach(function(row) {
+            newrow = [];
+            row.forEach(function(c) {
+                if (typeof(c.remove) == 'undefined') {
+                    newrow.push(c);
+                }
+                
+            });
+            if (newrow.length > 0) {
+                newrows.push(row);
+            }
+        });
+        this.rows =  newrows;
+        
+        
+        
+        this.no_row--;
+        this.updateElement();
+        
+    },
+    removeRow : function()
+    {
+        this.deleteRow({
+            type: 'row',
+            row : this.no_row-1
+        });
+        
+    },
+    
+     
+    addRow : function()
+    {
+        
+        var row = [];
+        for (var i = 0; i < this.no_col; i++ ) {
+            
+            row.push(this.emptyCell());
+           
+        }
+        this.rows.push(row);
+        this.updateElement();
+        
+    },
+     
+    // the default cell object... at present...
+    emptyCell : function() {
+        return (new Roo.htmleditor.BlockTd({})).toObject();
+        
+     
+    },
+    
+    removeNode : function()
+    {
+        return this.node;
+    },
+    
+    
+    
+    resetWidths : function()
+    {
+        Array.from(this.node.getElementsByTagName('td')).forEach(function(n) {
+            var nn = Roo.htmleditor.Block.factory(n);
+            nn.width = '';
+            nn.updateElement(n);
+        });
+    }
+    
+    
+    
+    
+})
+
+/**
+ *
+ * editing a TD?
+ *
+ * since selections really work on the table cell, then editing really should work from there
+ *
+ * The original plan was to support merging etc... - but that may not be needed yet..
+ *
+ * So this simple version will support:
+ *   add/remove cols
+ *   adjust the width +/-
+ *   reset the width...
+ *   
+ *
+ */
+
+
+
+/**
+ * @class Roo.htmleditor.BlockTable
+ * Block that manages a table
+ * 
+ * @constructor
+ * Create a new Filter.
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.BlockTd = function(cfg)
+{
+    if (cfg.node) {
+        this.readElement(cfg.node);
+        this.updateElement(cfg.node);
+    }
+    Roo.apply(this, cfg);
+     
+    
+    
+}
+Roo.extend(Roo.htmleditor.BlockTd, Roo.htmleditor.Block, {
+    node : false,
+    
+    width: '',
+    textAlign : 'left',
+    valign : 'top',
+    
+    colspan : 1,
+    rowspan : 1,
+    
+    
+    // used by context menu
+    friendly_name : 'Table Cell',
+    deleteTitle : false, // use our customer delete
+    
+    // context menu is drawn once..
+    
+    contextMenu : function(toolbar)
+    {
+        
+        var cell = function() {
+            return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode);
+        };
+        
+        var table = function() {
+            return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode.closest('table'));
+        };
+        
+        var lr = false;
+        var saveSel = function()
+        {
+            lr = toolbar.editorcore.getSelection().getRangeAt(0);
+        }
+        var restoreSel = function()
+        {
+            if (lr) {
+                (function() {
+                    toolbar.editorcore.focus();
+                    var cr = toolbar.editorcore.getSelection();
+                    cr.removeAllRanges();
+                    cr.addRange(lr);
+                    toolbar.editorcore.onEditorEvent();
+                }).defer(10, this);
+                
+                
+            }
+        }
+        
+        var rooui =  typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap;
+        
+        var syncValue = toolbar.editorcore.syncValue;
+        
+        var fields = {};
+        
+        return [
+            {
+                xtype : 'Button',
+                text : 'Edit Table',
+                listeners : {
+                    click : function() {
+                        var t = toolbar.tb.selectedNode.closest('table');
+                        toolbar.editorcore.selectNode(t);
+                        toolbar.editorcore.onEditorEvent();                        
+                    }
+                }
+                
+            },
+              
+           
+             
+            {
+                xtype : 'TextItem',
+                text : "Column Width: ",
+                 xns : rooui.Toolbar 
+               
+            },
+            {
+                xtype : 'Button',
+                text: '-',
+                listeners : {
+                    click : function (_self, e)
+                    {
+                        toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+                        cell().shrinkColumn();
+                        syncValue();
+                         toolbar.editorcore.onEditorEvent();
+                    }
+                },
+                xns : rooui.Toolbar
+            },
+            {
+                xtype : 'Button',
+                text: '+',
+                listeners : {
+                    click : function (_self, e)
+                    {
+                        toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+                        cell().growColumn();
+                        syncValue();
+                        toolbar.editorcore.onEditorEvent();
+                    }
+                },
+                xns : rooui.Toolbar
+            },
+            
+            {
+                xtype : 'TextItem',
+                text : "Vertical Align: ",
+                xns : rooui.Toolbar  //Boostrap?
+            },
+            {
+                xtype : 'ComboBox',
+                allowBlank : false,
+                displayField : 'val',
+                editable : true,
+                listWidth : 100,
+                triggerAction : 'all',
+                typeAhead : true,
+                valueField : 'val',
+                width : 100,
+                name : 'valign',
+                listeners : {
+                    select : function (combo, r, index)
+                    {
+                        toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+                        var b = cell();
+                        b.valign = r.get('val');
+                        b.updateElement();
+                        syncValue();
+                        toolbar.editorcore.onEditorEvent();
+                    }
+                },
+                xns : rooui.form,
+                store : {
+                    xtype : 'SimpleStore',
+                    data : [
+                        ['top'],
+                        ['middle'],
+                        ['bottom'] // there are afew more... 
+                    ],
+                    fields : [ 'val'],
+                    xns : Roo.data
+                }
+            },
+            
+            {
+                xtype : 'TextItem',
+                text : "Merge Cells: ",
+                 xns : rooui.Toolbar 
+               
+            },
+            
+            
+            {
+                xtype : 'Button',
+                text: 'Right',
+                listeners : {
+                    click : function (_self, e)
+                    {
+                        toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+                        cell().mergeRight();
+                        //block().growColumn();
+                        syncValue();
+                        toolbar.editorcore.onEditorEvent();
+                    }
+                },
+                xns : rooui.Toolbar
+            },
+             
+            {
+                xtype : 'Button',
+                text: 'Below',
+                listeners : {
+                    click : function (_self, e)
+                    {
+                        toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+                        cell().mergeBelow();
+                        //block().growColumn();
+                        syncValue();
+                        toolbar.editorcore.onEditorEvent();
+                    }
+                },
+                xns : rooui.Toolbar
+            },
+            {
+                xtype : 'TextItem',
+                text : "| ",
+                 xns : rooui.Toolbar 
+               
+            },
+            
+            {
+                xtype : 'Button',
+                text: 'Split',
+                listeners : {
+                    click : function (_self, e)
+                    {
+                        //toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+                        cell().split();
+                        syncValue();
+                        toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+                        toolbar.editorcore.onEditorEvent();
+                                             
+                    }
+                },
+                xns : rooui.Toolbar
+            },
+            {
+                xtype : 'Fill',
+                xns : rooui.Toolbar 
+               
+            },
+        
+          
+            {
+                xtype : 'Button',
+                text: 'Delete',
+                 
+                xns : rooui.Toolbar,
+                menu : {
+                    xtype : 'Menu',
+                    xns : rooui.menu,
+                    items : [
+                        {
+                            xtype : 'Item',
+                            html: 'Column',
+                            listeners : {
+                                click : function (_self, e)
+                                {
+                                    var t = table();
+                                    
+                                    cell().deleteColumn();
+                                    syncValue();
+                                    toolbar.editorcore.selectNode(t.node);
+                                    toolbar.editorcore.onEditorEvent();   
+                                }
+                            },
+                            xns : rooui.menu
+                        },
+                        {
+                            xtype : 'Item',
+                            html: 'Row',
+                            listeners : {
+                                click : function (_self, e)
+                                {
+                                    var t = table();
+                                    cell().deleteRow();
+                                    syncValue();
+                                    
+                                    toolbar.editorcore.selectNode(t.node);
+                                    toolbar.editorcore.onEditorEvent();   
+                                                         
+                                }
+                            },
+                            xns : rooui.menu
+                        },
+                       {
+                            xtype : 'Separator',
+                            xns : rooui.menu
+                        },
+                        {
+                            xtype : 'Item',
+                            html: 'Table',
+                            listeners : {
+                                click : function (_self, e)
+                                {
+                                    var t = table();
+                                    var nn = t.node.nextSibling || t.node.previousSibling;
+                                    t.node.parentNode.removeChild(t.node);
+                                    if (nn) { 
+                                        toolbar.editorcore.selectNode(nn, true);
+                                    }
+                                    toolbar.editorcore.onEditorEvent();   
+                                                         
+                                }
+                            },
+                            xns : rooui.menu
+                        }
+                    ]
+                }
+            }
+            
+            // align... << fixme
+            
+        ];
+        
+    },
+    
+    
+  /**
+     * create a DomHelper friendly object - for use with
+     * Roo.DomHelper.markup / overwrite / etc..
+     * ?? should it be called with option to hide all editing features?
+     */
+ /**
+     * create a DomHelper friendly object - for use with
+     * Roo.DomHelper.markup / overwrite / etc..
+     * ?? should it be called with option to hide all editing features?
+     */
+    toObject : function()
+    {
+        var ret = {
+            tag : 'td',
+            contenteditable : 'true', // this stops cell selection from picking the table.
+            'data-block' : 'Td',
+            valign : this.valign,
+            style : {  
+                'text-align' :  this.textAlign,
+                border : 'solid 1px rgb(0, 0, 0)', // ??? hard coded?
+                'border-collapse' : 'collapse',
+                padding : '6px', // 8 for desktop / 4 for mobile
+                'vertical-align': this.valign
+            },
+            html : this.html
+        };
+        if (this.width != '') {
+            ret.width = this.width;
+            ret.style.width = this.width;
+        }
+        
+        
+        if (this.colspan > 1) {
+            ret.colspan = this.colspan ;
+        } 
+        if (this.rowspan > 1) {
+            ret.rowspan = this.rowspan ;
+        }
+        
+           
+        
+        return ret;
+         
+    },
+    
+    readElement : function(node)
+    {
+        node  = node ? node : this.node ;
+        this.width = node.style.width;
+        this.colspan = Math.max(1,1*node.getAttribute('colspan'));
+        this.rowspan = Math.max(1,1*node.getAttribute('rowspan'));
+        this.html = node.innerHTML;
+        if (node.style.textAlign != '') {
+            this.textAlign = node.style.textAlign;
+        }
+        
+        
+    },
+     
+    // the default cell object... at present...
+    emptyCell : function() {
+        return {
+            colspan :  1,
+            rowspan :  1,
+            textAlign : 'left',
+            html : "&nbsp;" // is this going to be editable now?
+        };
+     
+    },
+    
+    removeNode : function()
+    {
+        return this.node.closest('table');
+         
+    },
+    
+    cellData : false,
+    
+    colWidths : false,
+    
+    toTableArray  : function()
+    {
+        var ret = [];
+        var tab = this.node.closest('tr').closest('table');
+        Array.from(tab.rows).forEach(function(r, ri){
+            ret[ri] = [];
+        });
+        var rn = 0;
+        this.colWidths = [];
+        var all_auto = true;
+        Array.from(tab.rows).forEach(function(r, ri){
+            
+            var cn = 0;
+            Array.from(r.cells).forEach(function(ce, ci){
+                var c =  {
+                    cell : ce,
+                    row : rn,
+                    col: cn,
+                    colspan : ce.colSpan,
+                    rowspan : ce.rowSpan
+                };
+                if (ce.isEqualNode(this.node)) {
+                    this.cellData = c;
+                }
+                // if we have been filled up by a row?
+                if (typeof(ret[rn][cn]) != 'undefined') {
+                    while(typeof(ret[rn][cn]) != 'undefined') {
+                        cn++;
+                    }
+                    c.col = cn;
+                }
+                
+                if (typeof(this.colWidths[cn]) == 'undefined' && c.colspan < 2) {
+                    this.colWidths[cn] =   ce.style.width;
+                    if (this.colWidths[cn] != '') {
+                        all_auto = false;
+                    }
+                }
+                
+                
+                if (c.colspan < 2 && c.rowspan < 2 ) {
+                    ret[rn][cn] = c;
+                    cn++;
+                    return;
+                }
+                for(var j = 0; j < c.rowspan; j++) {
+                    if (typeof(ret[rn+j]) == 'undefined') {
+                        continue; // we have a problem..
+                    }
+                    ret[rn+j][cn] = c;
+                    for(var i = 0; i < c.colspan; i++) {
+                        ret[rn+j][cn+i] = c;
+                    }
+                }
+                
+                cn += c.colspan;
+            }, this);
+            rn++;
+        }, this);
+        
+        // initalize widths.?
+        // either all widths or no widths..
+        if (all_auto) {
+            this.colWidths[0] = false; // no widths flag.
+        }
+        
+        
+        return ret;
+        
+    },
+    
+    
+    
+    
+    mergeRight: function()
+    {
+         
+        // get the contents of the next cell along..
+        var tr = this.node.closest('tr');
+        var i = Array.prototype.indexOf.call(tr.childNodes, this.node);
+        if (i >= tr.childNodes.length - 1) {
+            return; // no cells on right to merge with.
+        }
+        var table = this.toTableArray();
+        
+        if (typeof(table[this.cellData.row][this.cellData.col+this.cellData.colspan]) == 'undefined') {
+            return; // nothing right?
+        }
+        var rc = table[this.cellData.row][this.cellData.col+this.cellData.colspan];
+        // right cell - must be same rowspan and on the same row.
+        if (rc.rowspan != this.cellData.rowspan || rc.row != this.cellData.row) {
+            return; // right hand side is not same rowspan.
+        }
+        
+        
+        
+        this.node.innerHTML += ' ' + rc.cell.innerHTML;
+        tr.removeChild(rc.cell);
+        this.colspan += rc.colspan;
+        this.node.setAttribute('colspan', this.colspan);
+
+        var table = this.toTableArray();
+        this.normalizeWidths(table);
+        this.updateWidths(table);
+    },
+    
+    
+    mergeBelow : function()
+    {
+        var table = this.toTableArray();
+        if (typeof(table[this.cellData.row+this.cellData.rowspan]) == 'undefined') {
+            return; // no row below
+        }
+        if (typeof(table[this.cellData.row+this.cellData.rowspan][this.cellData.col]) == 'undefined') {
+            return; // nothing right?
+        }
+        var rc = table[this.cellData.row+this.cellData.rowspan][this.cellData.col];
+        
+        if (rc.colspan != this.cellData.colspan || rc.col != this.cellData.col) {
+            return; // right hand side is not same rowspan.
+        }
+        this.node.innerHTML =  this.node.innerHTML + rc.cell.innerHTML ;
+        rc.cell.parentNode.removeChild(rc.cell);
+        this.rowspan += rc.rowspan;
+        this.node.setAttribute('rowspan', this.rowspan);
+    },
+    
+    split: function()
+    {
+        if (this.node.rowSpan < 2 && this.node.colSpan < 2) {
+            return;
+        }
+        var table = this.toTableArray();
+        var cd = this.cellData;
+        this.rowspan = 1;
+        this.colspan = 1;
+        
+        for(var r = cd.row; r < cd.row + cd.rowspan; r++) {
+             
+            
+            for(var c = cd.col; c < cd.col + cd.colspan; c++) {
+                if (r == cd.row && c == cd.col) {
+                    this.node.removeAttribute('rowspan');
+                    this.node.removeAttribute('colspan');
+                }
+                 
+                var ntd = this.node.cloneNode(); // which col/row should be 0..
+                ntd.removeAttribute('id'); 
+                ntd.style.width  = this.colWidths[c];
+                ntd.innerHTML = '';
+                table[r][c] = { cell : ntd, col : c, row: r , colspan : 1 , rowspan : 1   };
+            }
+            
+        }
+        this.redrawAllCells(table);
+        
+    },
+    
+    
+    
+    redrawAllCells: function(table)
+    {
+        
+         
+        var tab = this.node.closest('tr').closest('table');
+        var ctr = tab.rows[0].parentNode;
+        Array.from(tab.rows).forEach(function(r, ri){
+            
+            Array.from(r.cells).forEach(function(ce, ci){
+                ce.parentNode.removeChild(ce);
+            });
+            r.parentNode.removeChild(r);
+        });
+        for(var r = 0 ; r < table.length; r++) {
+            var re = tab.rows[r];
+            
+            var re = tab.ownerDocument.createElement('tr');
+            ctr.appendChild(re);
+            for(var c = 0 ; c < table[r].length; c++) {
+                if (table[r][c].cell === false) {
+                    continue;
+                }
+                
+                re.appendChild(table[r][c].cell);
+                 
+                table[r][c].cell = false;
+            }
+        }
+        
+    },
+    updateWidths : function(table)
+    {
+        for(var r = 0 ; r < table.length; r++) {
+           
+            for(var c = 0 ; c < table[r].length; c++) {
+                if (table[r][c].cell === false) {
+                    continue;
+                }
+                
+                if (this.colWidths[0] != false && table[r][c].colspan < 2) {
+                    var el = Roo.htmleditor.Block.factory(table[r][c].cell);
+                    el.width = Math.floor(this.colWidths[c])  +'%';
+                    el.updateElement(el.node);
+                }
+                if (this.colWidths[0] != false && table[r][c].colspan > 1) {
+                    var el = Roo.htmleditor.Block.factory(table[r][c].cell);
+                    var width = 0;
+                    for(var i = 0; i < table[r][c].colspan; i ++) {
+                        width += Math.floor(this.colWidths[c + i]);
+                    }
+                    el.width = width  +'%';
+                    el.updateElement(el.node);
+                }
+                table[r][c].cell = false; // done
+            }
+        }
+    },
+    normalizeWidths : function(table)
+    {
+        if (this.colWidths[0] === false) {
+            var nw = 100.0 / this.colWidths.length;
+            this.colWidths.forEach(function(w,i) {
+                this.colWidths[i] = nw;
+            },this);
+            return;
+        }
+    
+        var t = 0, missing = [];
+        
+        this.colWidths.forEach(function(w,i) {
+            //if you mix % and
+            this.colWidths[i] = this.colWidths[i] == '' ? 0 : (this.colWidths[i]+'').replace(/[^0-9]+/g,'')*1;
+            var add =  this.colWidths[i];
+            if (add > 0) {
+                t+=add;
+                return;
+            }
+            missing.push(i);
+            
+            
+        },this);
+        var nc = this.colWidths.length;
+        if (missing.length) {
+            var mult = (nc - missing.length) / (1.0 * nc);
+            var t = mult * t;
+            var ew = (100 -t) / (1.0 * missing.length);
+            this.colWidths.forEach(function(w,i) {
+                if (w > 0) {
+                    this.colWidths[i] = w * mult;
+                    return;
+                }
+                
+                this.colWidths[i] = ew;
+            }, this);
+            // have to make up numbers..
+             
+        }
+        // now we should have all the widths..
+        
+    
+    },
+    
+    shrinkColumn : function()
+    {
+        var table = this.toTableArray();
+        this.normalizeWidths(table);
+        var col = this.cellData.col;
+        var nw = this.colWidths[col] * 0.8;
+        if (nw < 5) {
+            return;
+        }
+        var otherAdd = (this.colWidths[col]  * 0.2) / (this.colWidths.length -1);
+        this.colWidths.forEach(function(w,i) {
+            if (i == col) {
+                 this.colWidths[i] = nw;
+                return;
+            }
+            this.colWidths[i] += otherAdd
+        }, this);
+        this.updateWidths(table);
+         
+    },
+    growColumn : function()
+    {
+        var table = this.toTableArray();
+        this.normalizeWidths(table);
+        var col = this.cellData.col;
+        var nw = this.colWidths[col] * 1.2;
+        if (nw > 90) {
+            return;
+        }
+        var otherSub = (this.colWidths[col]  * 0.2) / (this.colWidths.length -1);
+        this.colWidths.forEach(function(w,i) {
+            if (i == col) {
+                this.colWidths[i] = nw;
+                return;
+            }
+            this.colWidths[i] -= otherSub
+        }, this);
+        this.updateWidths(table);
+         
+    },
+    deleteRow : function()
+    {
+        // delete this rows 'tr'
+        // if any of the cells in this row have a rowspan > 1 && row!= this row..
+        // then reduce the rowspan.
+        var table = this.toTableArray();
+        // this.cellData.row;
+        for (var i =0;i< table[this.cellData.row].length ; i++) {
+            var c = table[this.cellData.row][i];
+            if (c.row != this.cellData.row) {
+                
+                c.rowspan--;
+                c.cell.setAttribute('rowspan', c.rowspan);
+                continue;
+            }
+            if (c.rowspan > 1) {
+                c.rowspan--;
+                c.cell.setAttribute('rowspan', c.rowspan);
+            }
+        }
+        table.splice(this.cellData.row,1);
+        this.redrawAllCells(table);
+        
+    },
+    deleteColumn : function()
+    {
+        var table = this.toTableArray();
+        
+        for (var i =0;i< table.length ; i++) {
+            var c = table[i][this.cellData.col];
+            if (c.col != this.cellData.col) {
+                table[i][this.cellData.col].colspan--;
+            } else if (c.colspan > 1) {
+                c.colspan--;
+                c.cell.setAttribute('colspan', c.colspan);
+            }
+            table[i].splice(this.cellData.col,1);
+        }
+        
+        this.redrawAllCells(table);
+    }
+    
+    
+    
+    
+})
+
+//<script type="text/javascript">
+
+/*
+ * Based  Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ * LGPL
+ *
+ */
+/**
+ * @class Roo.HtmlEditorCore
+ * @extends Roo.Component
+ * Provides a the editing component for the HTML editors in Roo. (bootstrap and Roo.form)
+ *
+ * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
+ */
+
+Roo.HtmlEditorCore = function(config){
+    
+    
+    Roo.HtmlEditorCore.superclass.constructor.call(this, config);
+    
+    
+    this.addEvents({
+        /**
+         * @event initialize
+         * Fires when the editor is fully initialized (including the iframe)
+         * @param {Roo.HtmlEditorCore} this
+         */
+        initialize: true,
+        /**
+         * @event activate
+         * Fires when the editor is first receives the focus. Any insertion must wait
+         * until after this event.
+         * @param {Roo.HtmlEditorCore} this
+         */
+        activate: true,
+         /**
+         * @event beforesync
+         * Fires before the textarea is updated with content from the editor iframe. Return false
+         * to cancel the sync.
+         * @param {Roo.HtmlEditorCore} this
+         * @param {String} html
+         */
+        beforesync: true,
+         /**
+         * @event beforepush
+         * Fires before the iframe editor is updated with content from the textarea. Return false
+         * to cancel the push.
+         * @param {Roo.HtmlEditorCore} this
+         * @param {String} html
+         */
+        beforepush: true,
+         /**
+         * @event sync
+         * Fires when the textarea is updated with content from the editor iframe.
+         * @param {Roo.HtmlEditorCore} this
+         * @param {String} html
+         */
+        sync: true,
+         /**
+         * @event push
+         * Fires when the iframe editor is updated with content from the textarea.
+         * @param {Roo.HtmlEditorCore} this
+         * @param {String} html
+         */
+        push: true,
+        
+        /**
+         * @event editorevent
+         * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
+         * @param {Roo.HtmlEditorCore} this
+         */
+        editorevent: true 
+         
+        
+    });
+    
+    // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
+    
+    // defaults : white / black...
+    this.applyBlacklists();
+    
+    
+    
+};
+
+
+Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
+
+
+     /**
+     * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field 
+     */
+    
+    owner : false,
+    
+     /**
+     * @cfg {String} css styling for resizing. (used on bootstrap only)
+     */
+    resize : false,
+     /**
+     * @cfg {Number} height (in pixels)
+     */   
+    height: 300,
+   /**
+     * @cfg {Number} width (in pixels)
+     */   
+    width: 500,
+     /**
+     * @cfg {boolean} autoClean - default true - loading and saving will remove quite a bit of formating,
+     *         if you are doing an email editor, this probably needs disabling, it's designed
+     */
+    autoClean: true,
+    
+    /**
+     * @cfg {boolean} enableBlocks - default true - if the block editor (table and figure should be enabled)
+     */
+    enableBlocks : true,
+    /**
+     * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
+     * 
+     */
+    stylesheets: false,
+     /**
+     * @cfg {String} language default en - language of text (usefull for rtl languages)
+     * 
+     */
+    language: 'en',
+    
+    /**
+     * @cfg {boolean} allowComments - default false - allow comments in HTML source
+     *          - by default they are stripped - if you are editing email you may need this.
+     */
+    allowComments: false,
+    // id of frame..
+    frameId: false,
+    
+    // private properties
+    validationEvent : false,
+    deferHeight: true,
+    initialized : false,
+    activated : false,
+    sourceEditMode : false,
+    onFocus : Roo.emptyFn,
+    iframePad:3,
+    hideMode:'offsets',
+    
+    clearUp: true,
+    
+    // blacklist + whitelisted elements..
+    black: false,
+    white: false,
+     
+    bodyCls : '',
+
+    
+    undoManager : false,
+    /**
+     * Protected method that will not generally be called directly. It
+     * is called when the editor initializes the iframe with HTML contents. Override this method if you
+     * want to change the initialization markup of the iframe (e.g. to add stylesheets).
+     */
+    getDocMarkup : function(){
+        // body styles..
+        var st = '';
+        
+        // inherit styels from page...?? 
+        if (this.stylesheets === false) {
+            
+            Roo.get(document.head).select('style').each(function(node) {
+                st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
+            });
+            
+            Roo.get(document.head).select('link').each(function(node) { 
+                st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
+            });
+            
+        } else if (!this.stylesheets.length) {
+                // simple..
+                st = '<style type="text/css">' +
+                    'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
+                   '</style>';
+        } else {
+            for (var i in this.stylesheets) {
+                if (typeof(this.stylesheets[i]) != 'string') {
+                    continue;
+                }
+                st += '<link rel="stylesheet" href="' + this.stylesheets[i] +'" type="text/css">';
+            }
+            
+        }
+        
+        st +=  '<style type="text/css">' +
+            'IMG { cursor: pointer } ' +
+        '</style>';
+        
+        st += '<meta name="google" content="notranslate">';
+        
+        var cls = 'notranslate roo-htmleditor-body';
+        
+        if(this.bodyCls.length){
+            cls += ' ' + this.bodyCls;
+        }
+        
+        return '<html  class="notranslate" translate="no"><head>' + st  +
+            //<style type="text/css">' +
+            //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
+            //'</style>' +
+            ' </head><body contenteditable="true" data-enable-grammerly="true" class="' +  cls + '"></body></html>';
+    },
+
+    // private
+    onRender : function(ct, position)
+    {
+        var _t = this;
+        //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
+        this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
+        
+        
+        this.el.dom.style.border = '0 none';
+        this.el.dom.setAttribute('tabIndex', -1);
+        this.el.addClass('x-hidden hide');
+        
+        
+        
+        if(Roo.isIE){ // fix IE 1px bogus margin
+            this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
+        }
+       
+        
+        this.frameId = Roo.id();
+        
+        var ifcfg = {
+            tag: 'iframe',
+            cls: 'form-control', // bootstrap..
+            id: this.frameId,
+            name: this.frameId,
+            frameBorder : 'no',
+            'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL  :  "javascript:false"
+        };
+        if (this.resize) {
+            ifcfg.style = { resize : this.resize };
+        }
+        
+        var iframe = this.owner.wrap.createChild(ifcfg, this.el); 
+        
+        
+        this.iframe = iframe.dom;
+
+        this.assignDocWin();
+        
+        this.doc.designMode = 'on';
+       
+        this.doc.open();
+        this.doc.write(this.getDocMarkup());
+        this.doc.close();
+
+        
+        var task = { // must defer to wait for browser to be ready
+            run : function(){
+                //console.log("run task?" + this.doc.readyState);
+                this.assignDocWin();
+                if(this.doc.body || this.doc.readyState == 'complete'){
+                    try {
+                        this.doc.designMode="on";
+                        
+                    } catch (e) {
+                        return;
+                    }
+                    Roo.TaskMgr.stop(task);
+                    this.initEditor.defer(10, this);
+                }
+            },
+            interval : 10,
+            duration: 10000,
+            scope: this
+        };
+        Roo.TaskMgr.start(task);
+
+    },
+
+    // private
+    onResize : function(w, h)
     {
          Roo.log('resize: ' +w + ',' + h );
         //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
@@ -26243,10 +31085,10 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
         
         if(this.sourceEditMode){
  
-            Roo.get(this.iframe).addClass(['x-hidden','hide']);     //FIXME - what's the BS styles for these
+            Roo.get(this.iframe).addClass(['x-hidden','hide', 'd-none']);     //FIXME - what's the BS styles for these
             
         }else{
-            Roo.get(this.iframe).removeClass(['x-hidden','hide']);
+            Roo.get(this.iframe).removeClass(['x-hidden','hide', 'd-none']);
             //this.iframe.className = '';
             this.deferFocus();
         }
@@ -26263,7 +31105,8 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
      * @param {String} html The HTML to be cleaned
      * return {String} The cleaned HTML
      */
-    cleanHtml : function(html){
+    cleanHtml : function(html)
+    {
         html = String(html);
         if(html.length > 5){
             if(Roo.isSafari){ // strip safari nonsense
@@ -26281,11 +31124,71 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
      * Protected method that will not generally be called directly. Syncs the contents
      * of the editor iframe with the textarea.
      */
-    syncValue : function(){
+    syncValue : function()
+    {
+        //Roo.log("HtmlEditorCore:syncValue (EDITOR->TEXT)");
         if(this.initialized){
+            
+            if (this.undoManager) {
+                this.undoManager.addEvent();
+            }
+
+            
             var bd = (this.doc.body || this.doc.documentElement);
-            //this.cleanUpPaste(); -- this is done else where and causes havoc..
-            var html = bd.innerHTML;
+           
+            
+            var sel = this.win.getSelection();
+            
+            var div = document.createElement('div');
+            div.innerHTML = bd.innerHTML;
+            var gtx = div.getElementsByClassName('gtx-trans-icon'); // google translate - really annoying and difficult to get rid of.
+            if (gtx.length > 0) {
+                var rm = gtx.item(0).parentNode;
+                rm.parentNode.removeChild(rm);
+            }
+            
+           
+            if (this.enableBlocks) {
+                new Roo.htmleditor.FilterBlock({ node : div });
+            }
+            
+            var html = div.innerHTML;
+            
+            //?? tidy?
+            if (this.autoClean) {
+                
+                new Roo.htmleditor.FilterAttributes({
+                    node : div,
+                    attrib_white : [
+                            'href',
+                            'src',
+                            'name',
+                            'align',
+                            'colspan',
+                            'rowspan',
+                            'data-display',
+                            'data-width',
+                            'start' ,
+                            'style',
+                            // youtube embed.
+                            'class',
+                            'allowfullscreen',
+                            'frameborder',
+                            'width',
+                            'height',
+                            'alt'
+                            ],
+                    attrib_clean : ['href', 'src' ] 
+                });
+                
+                var tidy = new Roo.htmleditor.TidySerializer({
+                    inner:  true
+                });
+                html  = tidy.serialize(div);
+                
+            }
+            
+            
             if(Roo.isSafari){
                 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
                 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
@@ -26330,24 +31233,41 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
     },
 
     /**
+     * TEXTAREA -> EDITABLE
      * Protected method that will not generally be called directly. Pushes the value of the textarea
      * into the iframe editor.
      */
-    pushValue : function(){
+    pushValue : function()
+    {
+        //Roo.log("HtmlEditorCore:pushValue (TEXT->EDITOR)");
         if(this.initialized){
             var v = this.el.dom.value.trim();
             
-//            if(v.length < 1){
-//                v = '&#160;';
-//            }
             
             if(this.owner.fireEvent('beforepush', this, v) !== false){
                 var d = (this.doc.body || this.doc.documentElement);
                 d.innerHTML = v;
-                this.cleanUpPaste();
+                 
                 this.el.dom.value = d.innerHTML;
                 this.owner.fireEvent('push', this, v);
             }
+            if (this.autoClean) {
+                new Roo.htmleditor.FilterParagraph({node : this.doc.body}); // paragraphs
+                new Roo.htmleditor.FilterSpan({node : this.doc.body}); // empty spans
+            }
+            if (this.enableBlocks) {
+                Roo.htmleditor.Block.initAll(this.doc.body);
+            }
+            
+            this.updateLanguage();
+            
+            var lc = this.doc.body.lastChild;
+            if (lc && lc.nodeType == 1 && lc.getAttribute("contenteditable") == "false") {
+                // add an extra line at the end.
+                this.doc.body.appendChild(this.doc.createElement('br'));
+            }
+            
+            
         }
     },
 
@@ -26409,28 +31329,182 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
         //var ss = this.el.getStyles( 'background-image', 'background-repeat');
         //ss['background-attachment'] = 'fixed'; // w3c
         dbody.bgProperties = 'fixed'; // ie
+        dbody.setAttribute("translate", "no");
+        
         //Roo.DomHelper.applyStyles(dbody, ss);
         Roo.EventManager.on(this.doc, {
-            //'mousedown': this.onEditorEvent,
+             
             'mouseup': this.onEditorEvent,
             'dblclick': this.onEditorEvent,
             'click': this.onEditorEvent,
             'keyup': this.onEditorEvent,
+            
             buffer:100,
             scope: this
         });
+        Roo.EventManager.on(this.doc, {
+            'paste': this.onPasteEvent,
+            scope : this
+        });
         if(Roo.isGecko){
             Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
         }
+        //??? needed???
         if(Roo.isIE || Roo.isSafari || Roo.isOpera){
             Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
         }
         this.initialized = true;
 
+        
+        // initialize special key events - enter
+        new Roo.htmleditor.KeyEnter({core : this});
+        
+         
+        
         this.owner.fireEvent('initialize', this);
         this.pushValue();
     },
-
+    // this is to prevent a href clicks resulting in a redirect?
+   
+    onPasteEvent : function(e,v)
+    {
+        // I think we better assume paste is going to be a dirty load of rubish from word..
+        
+        // even pasting into a 'email version' of this widget will have to clean up that mess.
+        var cd = (e.browserEvent.clipboardData || window.clipboardData);
+        
+        // check what type of paste - if it's an image, then handle it differently.
+        if (cd.files && cd.files.length > 0) {
+            // pasting images?
+            var urlAPI = (window.createObjectURL && window) || 
+                (window.URL && URL.revokeObjectURL && URL) || 
+                (window.webkitURL && webkitURL);
+            
+            var r = new FileReader();
+            var t = this;
+            r.addEventListener('load',function()
+            {
+                
+                var d = (new DOMParser().parseFromString('<img src="' + r.result+ '">', 'text/html')).body;
+                // is insert asycn?
+                if (t.enableBlocks) {
+                    
+                    Array.from(d.getElementsByTagName('img')).forEach(function(img) {
+                        if (img.closest('figure')) { // assume!! that it's aready
+                            return;
+                        }
+                        var fig  = new Roo.htmleditor.BlockFigure({
+                            image_src  : img.src
+                        });
+                        fig.updateElement(img); // replace it..
+                        
+                    });
+                }
+                t.insertAtCursor(d.innerHTML.replace(/&nbsp;/g,' '));
+                t.owner.fireEvent('paste', this);
+            });
+            r.readAsDataURL(cd.files[0]);
+            
+            e.preventDefault();
+            
+            return false;
+        }
+        if (cd.types.indexOf('text/html') < 0 ) {
+            return false;
+        }
+        var images = [];
+        var html = cd.getData('text/html'); // clipboard event
+        if (cd.types.indexOf('text/rtf') > -1) {
+            var parser = new Roo.rtf.Parser(cd.getData('text/rtf'));
+            images = parser.doc ? parser.doc.getElementsByType('pict') : [];
+        }
+        //Roo.log(images);
+        //Roo.log(imgs);
+        // fixme..
+        images = images.filter(function(g) { return !g.path.match(/^rtf\/(head|pgdsctbl|listtable|footerf)/); }) // ignore headers/footers etc.
+                       .map(function(g) { return g.toDataURL(); })
+                       .filter(function(g) { return g != 'about:blank'; });
+        
+        //Roo.log(html);
+        html = this.cleanWordChars(html);
+        
+        var d = (new DOMParser().parseFromString(html, 'text/html')).body;
+        
+        
+        var sn = this.getParentElement();
+        // check if d contains a table, and prevent nesting??
+        //Roo.log(d.getElementsByTagName('table'));
+        //Roo.log(sn);
+        //Roo.log(sn.closest('table'));
+        if (d.getElementsByTagName('table').length && sn && sn.closest('table')) {
+            e.preventDefault();
+            this.insertAtCursor("You can not nest tables");
+            //Roo.log("prevent?"); // fixme - 
+            return false;
+        }
+        
+        
+        
+        if (images.length > 0) {
+            // replace all v:imagedata - with img.
+            var ar = Array.from(d.getElementsByTagName('v:imagedata'));
+            Roo.each(ar, function(node) {
+                node.parentNode.insertBefore(d.ownerDocument.createElement('img'), node );
+                node.parentNode.removeChild(node);
+            });
+            
+            
+            Roo.each(d.getElementsByTagName('img'), function(img, i) {
+                img.setAttribute('src', images[i]);
+            });
+        }
+        if (this.autoClean) {
+            new Roo.htmleditor.FilterWord({ node : d });
+            
+            new Roo.htmleditor.FilterStyleToTag({ node : d });
+            new Roo.htmleditor.FilterAttributes({
+                node : d,
+                attrib_white : ['href', 'src', 'name', 'align', 'colspan', 'rowspan', 'data-display', 'data-width', 'start'],
+                attrib_clean : ['href', 'src' ] 
+            });
+            new Roo.htmleditor.FilterBlack({ node : d, tag : this.black});
+            // should be fonts..
+            new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT', ':' ]} );
+            new Roo.htmleditor.FilterParagraph({ node : d });
+            new Roo.htmleditor.FilterSpan({ node : d });
+            new Roo.htmleditor.FilterLongBr({ node : d });
+            new Roo.htmleditor.FilterComment({ node : d });
+            
+            
+        }
+        if (this.enableBlocks) {
+                
+            Array.from(d.getElementsByTagName('img')).forEach(function(img) {
+                if (img.closest('figure')) { // assume!! that it's aready
+                    return;
+                }
+                var fig  = new Roo.htmleditor.BlockFigure({
+                    image_src  : img.src
+                });
+                fig.updateElement(img); // replace it..
+                
+            });
+        }
+        
+        
+        this.insertAtCursor(d.innerHTML.replace(/&nbsp;/g,' '));
+        if (this.enableBlocks) {
+            Roo.htmleditor.Block.initAll(this.doc.body);
+        }
+         
+        
+        e.preventDefault();
+        this.owner.fireEvent('paste', this);
+        return false;
+        // default behaveiour should be our local cleanup paste? (optional?)
+        // for simple editor - we want to hammer the paste and get rid of everything... - so over-rideable..
+        //this.owner.fireEvent('paste', e, v);
+    },
     // private
     onDestroy : function(){
         
@@ -26452,7 +31526,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
     onFirstFocus : function(){
         
         this.assignDocWin();
-        
+        this.undoManager = new Roo.lib.UndoManager(100,(this.doc.body || this.doc.documentElement));
         
         this.activated = true;
          
@@ -26497,10 +31571,50 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
 
     onEditorEvent : function(e)
     {
-        this.owner.fireEvent('editorevent', this, e);
+         
+        
+        if (e && (e.ctrlKey || e.metaKey) && e.keyCode === 90) {
+            return; // we do not handle this.. (undo manager does..)
+        }
+        // clicking a 'block'?
+        
+        // in theory this detects if the last element is not a br, then we try and do that.
+        // its so clicking in space at bottom triggers adding a br and moving the cursor.
+        if (e &&
+            e.target.nodeName == 'BODY' &&
+            e.type == "mouseup" &&
+            this.doc.body.lastChild
+           ) {
+            var lc = this.doc.body.lastChild;
+            // gtx-trans is google translate plugin adding crap.
+            while ((lc.nodeType == 3 && lc.nodeValue == '') || lc.id == 'gtx-trans') {
+                lc = lc.previousSibling;
+            }
+            if (lc.nodeType == 1 && lc.nodeName != 'BR') {
+            // if last element is <BR> - then dont do anything.
+            
+                var ns = this.doc.createElement('br');
+                this.doc.body.appendChild(ns);
+                range = this.doc.createRange();
+                range.setStartAfter(ns);
+                range.collapse(true);
+                var sel = this.win.getSelection();
+                sel.removeAllRanges();
+                sel.addRange(range);
+            }
+        }
+        
+        
+        
+        this.fireEditorEvent(e);
       //  this.updateToolbar();
         this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
     },
+    
+    fireEditorEvent: function(e)
+    {
+        this.owner.fireEvent('editorevent', this, e);
+    },
 
     insertTag : function(tg)
     {
@@ -26522,7 +31636,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
             
         }
         this.execCmd("formatblock",   tg);
-        
+        this.undoManager.addEvent(); 
     },
     
     insertText : function(txt)
@@ -26534,6 +31648,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
                //alert(Sender.getAttribute('label'));
                
         range.insertNode(this.doc.createTextNode(txt));
+        this.undoManager.addEvent();
     } ,
     
      
@@ -26544,7 +31659,38 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
      * @param {String} cmd The Midas command
      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
      */
-    relayCmd : function(cmd, value){
+    relayCmd : function(cmd, value)
+    {
+        
+        switch (cmd) {
+            case 'justifyleft':
+            case 'justifyright':
+            case 'justifycenter':
+                // if we are in a cell, then we will adjust the
+                var n = this.getParentElement();
+                var td = n.closest('td');
+                if (td) {
+                    var bl = Roo.htmleditor.Block.factory(td);
+                    bl.textAlign = cmd.replace('justify','');
+                    bl.updateElement();
+                    this.owner.fireEvent('editorevent', this);
+                    return;
+                }
+                this.execCmd('styleWithCSS', true); // 
+                break;
+            case 'bold':
+            case 'italic':
+            case 'underline':                
+                // if there is no selection, then we insert, and set the curson inside it..
+                this.execCmd('styleWithCSS', false); 
+                break;
+                
+        
+            default:
+                break;
+        }
+        
+        
         this.win.focus();
         this.execCmd(cmd, value);
         this.owner.fireEvent('editorevent', this);
@@ -26577,20 +31723,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
         if(!this.activated){
             return;
         }
-        /*
-        if(Roo.isIE){
-            this.win.focus();
-            var r = this.doc.selection.createRange();
-            if(r){
-                r.collapse(true);
-                r.pasteHTML(text);
-                this.syncValue();
-                this.deferFocus();
-            
-            }
-            return;
-        }
-        */
+         
         if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
             this.win.focus();
             
@@ -26600,19 +31733,31 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
             var win = this.win;
             
             if (win.getSelection && win.getSelection().getRangeAt) {
+                
+                // delete the existing?
+                
+                this.createRange(this.getSelection()).deleteContents();
                 range = win.getSelection().getRangeAt(0);
                 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
                 range.insertNode(node);
+                range = range.cloneRange();
+                range.collapse(false);
+                 
+                win.getSelection().removeAllRanges();
+                win.getSelection().addRange(range);
+                
+                
+                
             } else if (win.document.selection && win.document.selection.createRange) {
                 // no firefox support
                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
                 win.document.selection.createRange().pasteHTML(txt);
+            
             } else {
                 // no firefox support
                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
                 this.execCmd('InsertHTML', txt);
             } 
-            
             this.syncValue();
             
             this.deferFocus();
@@ -26637,15 +31782,17 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
                         cmd = 'underline';
                         break;
                     
-                    case 'v':
-                        this.cleanUpPaste.defer(100, this);
-                        return;
+                    //case 'v':
+                      //  this.cleanUpPaste.defer(100, this);
+                      //  return;
                         
                 }
                 if(cmd){
-                    this.win.focus();
-                    this.execCmd(cmd);
-                    this.deferFocus();
+                    
+                    this.relayCmd(cmd);
+                    //this.win.focus();
+                    //this.execCmd(cmd);
+                    //this.deferFocus();
                     e.preventDefault();
                 }
                 
@@ -26655,6 +31802,8 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
 
     // private
     fixKeys : function(){ // load time branching for fastest keydown performance
+        
+        
         if(Roo.isIE){
             return function(e){
                 var k = e.getKey(), r;
@@ -26668,23 +31817,25 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
                     }
                     return;
                 }
-                
+                /// this is handled by Roo.htmleditor.KeyEnter
+                 /*
                 if(k == e.ENTER){
                     r = this.doc.selection.createRange();
                     if(r){
                         var target = r.parentElement();
                         if(!target || target.tagName.toLowerCase() != 'li'){
                             e.stopEvent();
-                            r.pasteHTML('<br />');
+                            r.pasteHTML('<br/>');
                             r.collapse(false);
                             r.select();
                         }
                     }
                 }
-                if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
-                    this.cleanUpPaste.defer(100, this);
-                    return;
-                }
+                */
+                //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
+                //    this.cleanUpPaste.defer(100, this);
+                //    return;
+                //}
                 
                 
             };
@@ -26697,10 +31848,11 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
                     this.execCmd('InsertHTML','&#160;&#160;&#160;&#160;');
                     this.deferFocus();
                 }
-                if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
-                    this.cleanUpPaste.defer(100, this);
-                    return;
-                }
+               
+                //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
+                //    this.cleanUpPaste.defer(100, this);
+                 //   return;
+                //}
                 
             };
         }else if(Roo.isSafari){
@@ -26713,10 +31865,12 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
                     this.deferFocus();
                     return;
                 }
-               if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
-                    this.cleanUpPaste.defer(100, this);
-                    return;
-                }
+                 this.mozKeyPress(e);
+                
+               //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
+                 //   this.cleanUpPaste.defer(100, this);
+                 //   return;
+               // }
                 
              };
         }
@@ -26746,7 +31900,27 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
     getSelection : function() 
     {
         this.assignDocWin();
-        return Roo.isIE ? this.doc.selection : this.win.getSelection();
+        return Roo.lib.Selection.wrap(Roo.isIE ? this.doc.selection : this.win.getSelection(), this.doc);
+    },
+    /**
+     * Select a dom node
+     * @param {DomElement} node the node to select
+     */
+    selectNode : function(node, collapse)
+    {
+        var nodeRange = node.ownerDocument.createRange();
+        try {
+            nodeRange.selectNode(node);
+        } catch (e) {
+            nodeRange.selectNodeContents(node);
+        }
+        if (collapse === true) {
+            nodeRange.collapse(true);
+        }
+        //
+        var s = this.win.getSelection();
+        s.removeAllRanges();
+        s.addRange(nodeRange);
     },
     
     getSelectedNode: function() 
@@ -26755,8 +31929,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
         
         // should we cache this!!!!
         
-        
-        
+         
          
         var range = this.createRange(this.getSelection()).cloneRange();
         
@@ -26820,6 +31993,8 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
         
         return nodes[0];
     },
+    
+    
     createRange: function(sel)
     {
         // this has strange effects when using with 
@@ -26937,26 +32112,21 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
         // fully contined.
         return 3;
     },
-
-    // private? - in a new class?
-    cleanUpPaste :  function()
-    {
-        // cleans up the whole document..
-        Roo.log('cleanuppaste');
-        
-        this.cleanUpChildren(this.doc.body);
-        var clean = this.cleanWordChars(this.doc.body.innerHTML);
-        if (clean != this.doc.body.innerHTML) {
-            this.doc.body.innerHTML = clean;
-        }
-        
-    },
-    
     cleanWordChars : function(input) {// change the chars to hex code
-        var he = Roo.HtmlEditorCore;
         
+       var swapCodes  = [ 
+            [    8211, "&#8211;" ], 
+            [    8212, "&#8212;" ], 
+            [    8216,  "'" ],  
+            [    8217, "'" ],  
+            [    8220, '"' ],  
+            [    8221, '"' ],  
+            [    8226, "*" ],  
+            [    8230, "..." ]
+        ]; 
         var output = input;
-        Roo.each(he.swapCodes, function(sw) { 
+        Roo.each(swapCodes, function(sw) { 
             var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
             
             output = output.replace(swapper, sw[1]);
@@ -26965,487 +32135,61 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
         return output;
     },
     
-    
-    cleanUpChildren : function (n)
-    {
-        if (!n.childNodes.length) {
-            return;
-        }
-        for (var i = n.childNodes.length-1; i > -1 ; i--) {
-           this.cleanUpChild(n.childNodes[i]);
-        }
-    },
-    
+     
     
         
     
     cleanUpChild : function (node)
     {
-        var ed = this;
-        //console.log(node);
-        if (node.nodeName == "#text") {
-            // clean up silly Windows -- stuff?
-            return; 
-        }
-        if (node.nodeName == "#comment") {
-            if (!this.allowComments) {
-                node.parentNode.removeChild(node);
-            }
-            // clean up silly Windows -- stuff?
-            return; 
-        }
-        var lcname = node.tagName.toLowerCase();
-        // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full
-        // whitelist of tags..
-        
-        if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
-            // remove node.
-            node.parentNode.removeChild(node);
-            return;
-            
-        }
-        
-        var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
-        
-        // spans with no attributes - just remove them..
-        if ((!node.attributes || !node.attributes.length) && lcname == 'span') { 
-            remove_keep_children = true;
-        }
-        
-        // remove <a name=....> as rendering on yahoo mailer is borked with this.
-        // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
-        
-        //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
-        //    remove_keep_children = true;
-        //}
-        
-        if (remove_keep_children) {
-            this.cleanUpChildren(node);
-            // inserts everything just before this node...
-            while (node.childNodes.length) {
-                var cn = node.childNodes[0];
-                node.removeChild(cn);
-                node.parentNode.insertBefore(cn, node);
-            }
-            node.parentNode.removeChild(node);
-            return;
-        }
-        
-        if (!node.attributes || !node.attributes.length) {
-            
-          
-            
-            
-            this.cleanUpChildren(node);
-            return;
-        }
-        
-        function cleanAttr(n,v)
-        {
-            
-            if (v.match(/^\./) || v.match(/^\//)) {
-                return;
-            }
-            if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/) || v.match(/^ftp:/)) {
-                return;
-            }
-            if (v.match(/^#/)) {
-                return;
-            }
-            if (v.match(/^\{/)) { // allow template editing.
-                return;
-            }
-//            Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
-            node.removeAttribute(n);
-            
-        }
-        
-        var cwhite = this.cwhite;
-        var cblack = this.cblack;
-            
-        function cleanStyle(n,v)
-        {
-            if (v.match(/expression/)) { //XSS?? should we even bother..
-                node.removeAttribute(n);
-                return;
-            }
-            
-            var parts = v.split(/;/);
-            var clean = [];
-            
-            Roo.each(parts, function(p) {
-                p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
-                if (!p.length) {
-                    return true;
-                }
-                var l = p.split(':').shift().replace(/\s+/g,'');
-                l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
-                
-                if ( cwhite.length && cblack.indexOf(l) > -1) {
-//                    Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
-                    //node.removeAttribute(n);
-                    return true;
-                }
-                //Roo.log()
-                // only allow 'c whitelisted system attributes'
-                if ( cwhite.length &&  cwhite.indexOf(l) < 0) {
-//                    Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
-                    //node.removeAttribute(n);
-                    return true;
-                }
-                
-                
-                 
-                
-                clean.push(p);
-                return true;
-            });
-            if (clean.length) { 
-                node.setAttribute(n, clean.join(';'));
-            } else {
-                node.removeAttribute(n);
-            }
-            
-        }
-        
-        
-        for (var i = node.attributes.length-1; i > -1 ; i--) {
-            var a = node.attributes[i];
-            //console.log(a);
-            
-            if (a.name.toLowerCase().substr(0,2)=='on')  {
-                node.removeAttribute(a.name);
-                continue;
-            }
-            if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
-                node.removeAttribute(a.name);
-                continue;
-            }
-            if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
-                cleanAttr(a.name,a.value); // fixme..
-                continue;
-            }
-            if (a.name == 'style') {
-                cleanStyle(a.name,a.value);
-                continue;
-            }
-            /// clean up MS crap..
-            // tecnically this should be a list of valid class'es..
-            
-            
-            if (a.name == 'class') {
-                if (a.value.match(/^Mso/)) {
-                    node.removeAttribute('class');
-                }
-                
-                if (a.value.match(/^body$/)) {
-                    node.removeAttribute('class');
-                }
-                continue;
-            }
-            
-            // style cleanup!?
-            // class cleanup?
-            
-        }
-        
-        
-        this.cleanUpChildren(node);
         
+        new Roo.htmleditor.FilterComment({node : node});
+        new Roo.htmleditor.FilterAttributes({
+                node : node,
+                attrib_black : this.ablack,
+                attrib_clean : this.aclean,
+                style_white : this.cwhite,
+                style_black : this.cblack
+        });
+        new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
+        new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
+         
         
     },
     
     /**
      * Clean up MS wordisms...
+     * @deprecated - use filter directly
      */
     cleanWord : function(node)
     {
-        if (!node) {
-            this.cleanWord(this.doc.body);
-            return;
-        }
-        
-        if(
-                node.nodeName == 'SPAN' &&
-                !node.hasAttributes() &&
-                node.childNodes.length == 1 &&
-                node.firstChild.nodeName == "#text"  
-        ) {
-            var textNode = node.firstChild;
-            node.removeChild(textNode);
-            if (node.getAttribute('lang') != 'zh-CN') {   // do not space pad on chinese characters..
-                node.parentNode.insertBefore(node.ownerDocument.createTextNode(" "), node);
-            }
-            node.parentNode.insertBefore(textNode, node);
-            if (node.getAttribute('lang') != 'zh-CN') {   // do not space pad on chinese characters..
-                node.parentNode.insertBefore(node.ownerDocument.createTextNode(" ") , node);
-            }
-            node.parentNode.removeChild(node);
-        }
-        
-        if (node.nodeName == "#text") {
-            // clean up silly Windows -- stuff?
-            return; 
-        }
-        if (node.nodeName == "#comment") {
-            node.parentNode.removeChild(node);
-            // clean up silly Windows -- stuff?
-            return; 
-        }
-        
-        if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
-            node.parentNode.removeChild(node);
-            return;
-        }
-        //Roo.log(node.tagName);
-        // remove - but keep children..
-        if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|v:|font)/)) {
-            //Roo.log('-- removed');
-            while (node.childNodes.length) {
-                var cn = node.childNodes[0];
-                node.removeChild(cn);
-                node.parentNode.insertBefore(cn, node);
-                // move node to parent - and clean it..
-                this.cleanWord(cn);
-            }
-            node.parentNode.removeChild(node);
-            /// no need to iterate chidlren = it's got none..
-            //this.iterateChildren(node, this.cleanWord);
-            return;
-        }
-        // clean styles
-        if (node.className.length) {
-            
-            var cn = node.className.split(/\W+/);
-            var cna = [];
-            Roo.each(cn, function(cls) {
-                if (cls.match(/Mso[a-zA-Z]+/)) {
-                    return;
-                }
-                cna.push(cls);
-            });
-            node.className = cna.length ? cna.join(' ') : '';
-            if (!cna.length) {
-                node.removeAttribute("class");
-            }
-        }
-        
-        if (node.hasAttribute("lang")) {
-            node.removeAttribute("lang");
-        }
-        
-        if (node.hasAttribute("style")) {
-            
-            var styles = node.getAttribute("style").split(";");
-            var nstyle = [];
-            Roo.each(styles, function(s) {
-                if (!s.match(/:/)) {
-                    return;
-                }
-                var kv = s.split(":");
-                if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
-                    return;
-                }
-                // what ever is left... we allow.
-                nstyle.push(s);
-            });
-            node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
-            if (!nstyle.length) {
-                node.removeAttribute('style');
-            }
-        }
-        this.iterateChildren(node, this.cleanWord);
-        
-        
+        new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
+        new Roo.htmleditor.FilterKeepChildren({node : node ? node : this.doc.body, tag : [ 'FONT', ':' ]} );
         
     },
-    /**
-     * iterateChildren of a Node, calling fn each time, using this as the scole..
-     * @param {DomNode} node node to iterate children of.
-     * @param {Function} fn method of this class to call on each item.
-     */
-    iterateChildren : function(node, fn)
-    {
-        if (!node.childNodes.length) {
-                return;
-        }
-        for (var i = node.childNodes.length-1; i > -1 ; i--) {
-           fn.call(this, node.childNodes[i])
-        }
-    },
-    
+   
     
     /**
-     * cleanTableWidths.
-     *
-     * Quite often pasting from word etc.. results in tables with column and widths.
-     * This does not work well on fluid HTML layouts - like emails. - so this code should hunt an destroy them..
-     *
+
+     * @deprecated - use filters
      */
     cleanTableWidths : function(node)
     {
-         
-         
-        if (!node) {
-            this.cleanTableWidths(this.doc.body);
-            return;
-        }
-        
-        // ignore list...
-        if (node.nodeName == "#text" || node.nodeName == "#comment") {
-            return; 
-        }
-        Roo.log(node.tagName);
-        if (!node.tagName.toLowerCase().match(/^(table|td|tr)$/)) {
-            this.iterateChildren(node, this.cleanTableWidths);
-            return;
-        }
-        if (node.hasAttribute('width')) {
-            node.removeAttribute('width');
-        }
-        
-         
-        if (node.hasAttribute("style")) {
-            // pretty basic...
-            
-            var styles = node.getAttribute("style").split(";");
-            var nstyle = [];
-            Roo.each(styles, function(s) {
-                if (!s.match(/:/)) {
-                    return;
-                }
-                var kv = s.split(":");
-                if (kv[0].match(/^\s*(width|min-width)\s*$/)) {
-                    return;
-                }
-                // what ever is left... we allow.
-                nstyle.push(s);
-            });
-            node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
-            if (!nstyle.length) {
-                node.removeAttribute('style');
-            }
-        }
-        
-        this.iterateChildren(node, this.cleanTableWidths);
-        
+        new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
         
     },
     
-    
-    
-    
-    domToHTML : function(currentElement, depth, nopadtext) {
-        
-        depth = depth || 0;
-        nopadtext = nopadtext || false;
-    
-        if (!currentElement) {
-            return this.domToHTML(this.doc.body);
-        }
-        
-        //Roo.log(currentElement);
-        var j;
-        var allText = false;
-        var nodeName = currentElement.nodeName;
-        var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
-        
-        if  (nodeName == '#text') {
-            
-            return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim();
-        }
-        
-        
-        var ret = '';
-        if (nodeName != 'BODY') {
-             
-            var i = 0;
-            // Prints the node tagName, such as <A>, <IMG>, etc
-            if (tagName) {
-                var attr = [];
-                for(i = 0; i < currentElement.attributes.length;i++) {
-                    // quoting?
-                    var aname = currentElement.attributes.item(i).name;
-                    if (!currentElement.attributes.item(i).value.length) {
-                        continue;
-                    }
-                    attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
-                }
-                
-                ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
-            } 
-            else {
-                
-                // eack
-            }
-        } else {
-            tagName = false;
-        }
-        if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
-            return ret;
-        }
-        if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
-            nopadtext = true;
-        }
-        
-        
-        // Traverse the tree
-        i = 0;
-        var currentElementChild = currentElement.childNodes.item(i);
-        var allText = true;
-        var innerHTML  = '';
-        lastnode = '';
-        while (currentElementChild) {
-            // Formatting code (indent the tree so it looks nice on the screen)
-            var nopad = nopadtext;
-            if (lastnode == 'SPAN') {
-                nopad  = true;
-            }
-            // text
-            if  (currentElementChild.nodeName == '#text') {
-                var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
-                toadd = nopadtext ? toadd : toadd.trim();
-                if (!nopad && toadd.length > 80) {
-                    innerHTML  += "\n" + (new Array( depth + 1 )).join( "  "  );
-                }
-                innerHTML  += toadd;
-                
-                i++;
-                currentElementChild = currentElement.childNodes.item(i);
-                lastNode = '';
-                continue;
-            }
-            allText = false;
-            
-            innerHTML  += nopad ? '' : "\n" + (new Array( depth + 1 )).join( "  "  );
-                
-            // Recursively traverse the tree structure of the child node
-            innerHTML   += this.domToHTML(currentElementChild, depth+1, nopadtext);
-            lastnode = currentElementChild.nodeName;
-            i++;
-            currentElementChild=currentElement.childNodes.item(i);
-        }
-        
-        ret += innerHTML;
-        
-        if (!allText) {
-                // The remaining code is mostly for formatting the tree
-            ret+= nopadtext ? '' : "\n" + (new Array( depth  )).join( "  "  );
-        }
-        
-        
-        if (tagName) {
-            ret+= "</"+tagName+">";
-        }
-        return ret;
-        
-    },
+     
         
     applyBlacklists : function()
     {
         var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white  : [];
         var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black :  [];
         
+        this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean :  Roo.HtmlEditorCore.aclean;
+        this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack :  Roo.HtmlEditorCore.ablack;
+        this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove :  Roo.HtmlEditorCore.tag_remove;
+        
         this.white = [];
         this.black = [];
         Roo.each(Roo.HtmlEditorCore.white, function(tag) {
@@ -27563,6 +32307,16 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
         
     },
     
+    
+    updateLanguage : function()
+    {
+        if (!this.iframe || !this.iframe.contentDocument) {
+            return;
+        }
+        Roo.get(this.iframe.contentDocument.body).attr("lang", this.language);
+    },
+    
+    
     removeStylesheets : function()
     {
         var _this = this;
@@ -27627,36 +32381,40 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
 });
 
 Roo.HtmlEditorCore.white = [
-        'area', 'br', 'img', 'input', 'hr', 'wbr',
+        'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
         
-       'address', 'blockquote', 'center', 'dd',      'dir',       'div', 
-       'dl',      'dt',         'h1',     'h2',      'h3',        'h4', 
-       'h5',      'h6',         'hr',     'isindex', 'listing',   'marquee', 
-       'menu',    'multicol',   'ol',     'p',       'plaintext', 'pre', 
-       'table',   'ul',         'xmp', 
+       'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD',      'DIR',       'DIV', 
+       'DL',      'DT',         'H1',     'H2',      'H3',        'H4', 
+       'H5',      'H6',         'HR',     'ISINDEX', 'LISTING',   'MARQUEE', 
+       'MENU',    'MULTICOL',   'OL',     'P',       'PLAINTEXT', 'PRE', 
+       'TABLE',   'UL',         'XMP', 
        
-       'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', 
-      'thead',   'tr', 
+       'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH', 
+      'THEAD',   'TR', 
      
-      'dir', 'menu', 'ol', 'ul', 'dl',
+      'DIR', 'MENU', 'OL', 'UL', 'DL',
        
-      'embed',  'object'
+      'EMBED',  'OBJECT'
 ];
 
 
 Roo.HtmlEditorCore.black = [
     //    'embed',  'object', // enable - backend responsiblity to clean thiese
-        'applet', // 
-        'base',   'basefont', 'bgsound', 'blink',  'body', 
-        'frame',  'frameset', 'head',    'html',   'ilayer', 
-        'iframe', 'layer',  'link',     'meta',    'object',   
-        'script', 'style' ,'title',  'xml' // clean later..
+        'APPLET', // 
+        'BASE',   'BASEFONT', 'BGSOUND', 'BLINK',  'BODY', 
+        'FRAME',  'FRAMESET', 'HEAD',    'HTML',   'ILAYER', 
+        'IFRAME', 'LAYER',  'LINK',     'META',    'OBJECT',   
+        'SCRIPT', 'STYLE' ,'TITLE',  'XML',
+        //'FONT' // CLEAN LATER..
+        'COLGROUP', 'COL'   // messy tables.
+        
+        
 ];
-Roo.HtmlEditorCore.clean = [
-    'script', 'style', 'title', 'xml'
+Roo.HtmlEditorCore.clean = [ // ?? needed???
+     'SCRIPT', 'STYLE', 'TITLE', 'XML'
 ];
-Roo.HtmlEditorCore.remove = [
-    'font'
+Roo.HtmlEditorCore.tag_remove = [
+    'FONT', 'TBODY'  
 ];
 // attributes..
 
@@ -27687,16 +32445,7 @@ Roo.HtmlEditorCore.cblack= [
 ];
 
 
-Roo.HtmlEditorCore.swapCodes   =[ 
-    [    8211, "&#8211;" ], 
-    [    8212, "&#8212;" ], 
-    [    8216,  "'" ],  
-    [    8217, "'" ],  
-    [    8220, '"' ],  
-    [    8221, '"' ],  
-    [    8226, "*" ],  
-    [    8230, "..." ]
-]; 
+
 
     /*
  * - LGPL
@@ -27805,9 +32554,9 @@ Roo.extend(Roo.bootstrap.form.HtmlEditor, Roo.bootstrap.form.TextArea,  {
     
     
       /**
-     * @cfg {Array} toolbars Array of toolbars. - defaults to just the Standard one
+     * @cfg {Array|boolean} toolbars Array of toolbars, or names of toolbars. - true for standard, and false for none.
      */
-    toolbars : false,
+    toolbars : true,
     
      /**
     * @cfg {Array} buttons Array of toolbar's buttons. - defaults to empty
@@ -27815,10 +32564,9 @@ Roo.extend(Roo.bootstrap.form.HtmlEditor, Roo.bootstrap.form.TextArea,  {
     btns : [],
    
      /**
-     * @cfg {String} resizable  's' or 'se' or 'e' - wrapps the element in a
-     *                        Roo.resizable.
+     * @cfg {String} resize  (none|both|horizontal|vertical) - css resize of element
      */
-    resizable : false,
+    resize : false,
      /**
      * @cfg {Number} height (in pixels)
      */   
@@ -27861,26 +32609,35 @@ Roo.extend(Roo.bootstrap.form.HtmlEditor, Roo.bootstrap.form.TextArea,  {
      * add custom toolbar buttons.
      * @param {HtmlEditor} editor
      */
-    createToolbar : function(){
-        Roo.log('renewing');
-        Roo.log("create toolbars");
+    createToolbar : function()
+    {
+        //Roo.log('renewing');
+        //Roo.log("create toolbars");
+        if (this.toolbars === false) {
+            return;
+        }
+        if (this.toolbars === true) {
+            this.toolbars = [ 'Standard' ];
+        }
         
-        this.toolbars = [ new Roo.bootstrap.form.HtmlEditorToolbarStandard({editor: this} ) ];
-        this.toolbars[0].render(this.toolbarContainer());
+        var ar = Array.from(this.toolbars);
+        this.toolbars = [];
+        ar.forEach(function(t,i) {
+            if (typeof(t) == 'string') {
+                t = {
+                    xtype : t
+                };
+            }
+            if (typeof(t) == 'object' && typeof(t.xtype) == 'string') {
+                t.editor = this;
+                t.xns = t.xns || Roo.bootstrap.form.HtmlEditorToolbar;
+                t = Roo.factory(t);
+            }
+            this.toolbars[i] = t;
+            this.toolbars[i].render(this.toolbarContainer());
+        }, this);
         
-        return;
         
-//        if (!editor.toolbars || !editor.toolbars.length) {
-//            editor.toolbars = [ new Roo.bootstrap.form.HtmlEditorToolbarStandard() ]; // can be empty?
-//        }
-//        
-//        for (var i =0 ; i < editor.toolbars.length;i++) {
-//            editor.toolbars[i] = Roo.factory(
-//                    typeof(editor.toolbars[i]) == 'string' ?
-//                        { xtype: editor.toolbars[i]} : editor.toolbars[i],
-//                Roo.bootstrap.form.HtmlEditor);
-//            editor.toolbars[i].init(editor);
-//        }
     },
 
      
@@ -27897,33 +32654,11 @@ Roo.extend(Roo.bootstrap.form.HtmlEditor, Roo.bootstrap.form.TextArea,  {
         
         this.editorcore.onRender(ct, position);
          
-        if (this.resizable) {
-            this.resizeEl = new Roo.Resizable(this.wrap, {
-                pinned : true,
-                wrap: true,
-                dynamic : true,
-                minHeight : this.height,
-                height: this.height,
-                handles : this.resizable,
-                width: this.width,
-                listeners : {
-                    resize : function(r, w, h) {
-                        _t.onResize(w,h); // -something
-                    }
-                }
-            });
-            
-        }
+         
         this.createToolbar(this);
        
         
-        if(!this.width && this.resizable){
-            this.setSize(this.wrap.getSize());
-        }
-        if (this.resizeEl) {
-            this.resizeEl.resizeTo.defer(100, this.resizeEl,[ this.width,this.height ] );
-            // should trigger onReize..
-        }
+          
         
     },
 
@@ -27994,9 +32729,9 @@ Roo.extend(Roo.bootstrap.form.HtmlEditor, Roo.bootstrap.form.TextArea,  {
             //this.deferFocus();
         }
          
-        if(this.resizable){
-            this.setSize(this.wrap.getSize());
-        }
+        //if(this.resizable){
+        //    this.setSize(this.wrap.getSize());
+        //}
         
         this.fireEvent('editmodechange', this, this.editorcore.sourceEditMode);
     },
@@ -28133,9 +32868,8 @@ Roo.extend(Roo.bootstrap.form.HtmlEditor, Roo.bootstrap.form.TextArea,  {
    
    
       
-Roo.namespace('Roo.bootstrap.form.HtmlEditor');
 /**
- * @class Roo.bootstrap.form.HtmlEditorToolbarStandard
+ * @class Roo.bootstrap.form.HtmlEditorToolbar.Standard
  * @parent Roo.bootstrap.form.HtmlEditor
  * @extends Roo.bootstrap.nav.Simplebar
  * Basic Toolbar
@@ -28146,7 +32880,7 @@ Roo.namespace('Roo.bootstrap.form.HtmlEditor');
  new Roo.bootstrap.form.HtmlEditor({
     ....
     toolbars : [
-        new Roo.bootstrap.form.HtmlEditorToolbarStandard({
+        new Roo.bootstrap.form.HtmlEditorToolbar.Standard({
             disable : { fonts: 1 , format: 1, ..., ... , ...],
             btns : [ .... ]
         })
@@ -28161,7 +32895,7 @@ Roo.namespace('Roo.bootstrap.form.HtmlEditor');
  * .x-html-editor-tb .x-edit-none .x-btn-text { background: none; }
  */
  
-Roo.bootstrap.form.HtmlEditorToolbarStandard = function(config)
+Roo.bootstrap.form.HtmlEditorToolbar.Standard = function(config)
 {
     
     Roo.apply(this, config);
@@ -28173,17 +32907,17 @@ Roo.bootstrap.form.HtmlEditorToolbarStandard = function(config)
         colors : true,
         specialElements : true
     });
-    Roo.bootstrap.form.HtmlEditorToolbarStandard.superclass.constructor.call(this, config);
+    Roo.bootstrap.form.HtmlEditorToolbar.Standard.superclass.constructor.call(this, config);
     
     this.editor = config.editor;
     this.editorcore = config.editor.editorcore;
     
-    this.buttons   = new Roo.util.MixedCollection(false, function(o) { return o.cmd; });
+    this.buttons   = new Roo.util.MixedCollection(false, function(o) { return o.btnid; });
     
     //Roo.form.HtmlEditorToolbar1.superclass.constructor.call(this, editor.wrap.dom.firstChild, [], config);
     // dont call parent... till later.
 }
-Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simplebar,  {
+Roo.extend(Roo.bootstrap.form.HtmlEditorToolbar.Standard, Roo.bootstrap.nav.Simplebar,  {
      
     bar : true,
     
@@ -28199,11 +32933,14 @@ Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simpl
         'div','span'
     ],
     
+    
+    deleteBtn: false,
+    
     onRender : function(ct, position)
     {
        // Roo.log("Call onRender: " + this.xtype);
         
-       Roo.bootstrap.form.HtmlEditorToolbarStandard.superclass.onRender.call(this, ct, position);
+       Roo.bootstrap.form.HtmlEditorToolbar.Standard.superclass.onRender.call(this, ct, position);
        Roo.log(this.el);
        this.el.dom.style.marginBottom = '0';
        var _this = this;
@@ -28211,7 +32948,7 @@ Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simpl
        var editor= this.editor;
        
        var children = [];
-       var btn = function(id,cmd , toggle, handler, html){
+       var btn = function(id, cmd , toggle, handler, html){
        
             var  event = toggle ? 'toggle' : 'click';
        
@@ -28220,9 +32957,11 @@ Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simpl
                 xtype: 'Button',
                 xns: Roo.bootstrap,
                 //glyphicon : id,
+                btnid : id,
                 fa: id,
-                cmd : id || cmd,
-                enableToggle:toggle !== false,
+                cls : 'roo-html-editor-btn-' + id,
+                cmd : cmd, // why id || cmd
+                enableToggle: toggle !== false,
                 html : html || '',
                 pressed : toggle ? false : null,
                 listeners : {}
@@ -28241,6 +32980,7 @@ Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simpl
                 size : 'sm',
                 xns: Roo.bootstrap,
                 fa : 'font',
+                cls : 'roo-html-editor-font-chooser',
                 //html : 'submit'
                 menu : {
                     xtype: 'Menu',
@@ -28266,19 +33006,19 @@ Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simpl
         });
         children.push(style);   
         
-        btn('bold',false,true);
-        btn('italic',false,true);
-        btn('align-left', 'justifyleft',true);
+        btn('bold',         'bold',true);
+        btn('italic',       'italic',true);
+        btn('underline',     'underline',true);
+        btn('align-left',   'justifyleft',true);
         btn('align-center', 'justifycenter',true);
         btn('align-right' , 'justifyright',true);
-        btn('link', false, false, function(btn) {
-            //Roo.log("create link?");
-            var url = prompt(this.createLinkText, this.defaultLinkValue);
-            if(url && url != 'http:/'+'/'){
-                this.editorcore.relayCmd('createlink', url);
-            }
-        }),
+        btn('link', false, true, this.onLinkClick);
+        
+        
+        btn('image', false, true, this.onImageClick);
         btn('list','insertunorderedlist',true);
+        btn('list-ol','insertorderedlist',true);
+
         btn('pencil', false,true, function(btn){
                 Roo.log(this);
                 this.toggleSourceEdit(btn.pressed);
@@ -28290,58 +33030,166 @@ Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simpl
             }
         }
         
-        /*
-        var cog = {
-                xtype: 'Button',
-                size : 'sm',
-                xns: Roo.bootstrap,
-                glyphicon : 'cog',
-                //html : 'submit'
-                menu : {
-                    xtype: 'Menu',
-                    xns: Roo.bootstrap,
-                    items:  []
-                }
-        };
-        
-        cog.menu.items.push({
-            xtype :'MenuItem',
-            xns: Roo.bootstrap,
-            html : Clean styles,
-            tagname : f,
-            listeners : {
-                click : function()
-                {
-                    editorcore.insertTag(this.tagname);
-                    editor.focus();
-                }
-            }
-            
-        });
-       */
         
          
-       this.xtype = 'NavSimplebar';
+        this.xtype = 'NavSimplebar'; // why?
         
         for(var i=0;i< children.length;i++) {
             
             this.buttons.add(this.addxtypeChild(children[i]));
             
         }
-        
+        this.buildToolbarDelete();
+
         editor.on('editorevent', this.updateToolbar, this);
     },
+    
+    buildToolbarDelete : function()
+    {
+        
+       /* this.addxtypeChild({
+            xtype : 'Element',
+            xns : Roo.bootstrap,
+            cls : 'roo-htmleditor-fill'
+        });
+        */
+        this.deleteBtn = this.addxtypeChild({
+            size : 'sm',
+            xtype: 'Button',
+            xns: Roo.bootstrap,
+            fa: 'eraser',
+            tooltip : "Clear Formating / Delete",
+            listeners : {
+                click : this.onDelete.createDelegate(this)
+            }
+        });
+        this.deleteBtn.hide();     
+        
+    },
+    
+    onImageClick : function()
+    {
+        if (this.input) {
+            this.input.un('change', this.onFileSelected, this);
+        }
+        this.input = Roo.get(document.body).createChild({ 
+          tag: 'input', 
+          type : 'file', 
+          style : 'display:none', 
+          multiple: 'multiple'
+       });
+        this.input.on('change', this.onFileSelected, this);
+        this.input.dom.click();
+    },
+    
+    onFileSelected : function(e)
+    {
+         e.preventDefault();
+        
+        if(typeof(this.input.dom.files) == 'undefined' || !this.input.dom.files.length){
+            return;
+        }
+    
+         
+        this.addFiles(Array.prototype.slice.call(this.input.dom.files));
+    },
+    
+    addFiles : function(far) {
+
+        if (!far.length) {
+            return;
+        }
+        
+        var f = far.pop();
+        
+        if (!f.type.match(/^image/)) {
+            this.addFiles(far);
+            return;
+        }
+         
+        var sn = this.selectedNode;
+        
+        var bl = sn  && this.editorcore.enableBlocks ? Roo.htmleditor.Block.factory(sn) : false;
+        
+        var editor =  this.editorcore;
+        
+        var reader = new FileReader();
+        reader.addEventListener('load', (function() {
+            if (bl) {
+                bl.image_src = reader.result;
+                //bl.caption = f.name;
+                bl.updateElement(sn);
+                editor.owner.fireEvent('editorevent', editor, false);
+                // we only do the first file!! and replace.
+                return;
+            }
+            if (this.editorcore.enableBlocks) {
+                var fig = new Roo.htmleditor.BlockFigure({
+                    image_src :  reader.result,
+                    caption : '',
+                    caption_display : 'none'  //default to hide captions..
+                 });
+                editor.insertAtCursor(fig.toHTML());
+                editor.owner.fireEvent('editorevent', editor, false);
+                return;
+            }
+            // just a standard img..
+            if (sn && sn.tagName.toUpperCase() == 'IMG') {
+                sn.src = reader.result;
+                editor.owner.fireEvent('editorevent', editor, false);
+                return;
+            }
+            editor.insertAtCursor('<img src="' + reader.result +'">');
+            editor.owner.fireEvent('editorevent', editor, false);
+            
+        }).createDelegate(this));
+        reader.readAsDataURL(f);
+        
+    
+     },
+    
+    
     onBtnClick : function(id)
     {
        this.editorcore.relayCmd(id);
        this.editorcore.focus();
     },
     
+    onLinkClick : function(btn) {
+        var url = this.selectedNode && this.selectedNode.tagName.toUpperCase() == 'A' ?
+                this.selectedNode.getAttribute('href') : '';
+            
+        Roo.bootstrap.MessageBox.show({
+            title : "Add / Edit Link URL",
+            msg : "Enter the URL for the link",
+            buttons: Roo.bootstrap.MessageBox.OKCANCEL,
+            minWidth: 250,
+            scope : this,
+            prompt:true,
+            multiline: false,
+            modal : true,
+            value : url,
+            fn:  function(pressed, newurl) {
+                if (pressed != 'ok') {
+                    this.editorcore.focus();
+                    return;
+                }
+                if (url != '') {
+                    this.selectedNode.setAttribute('href', newurl);
+                    return;
+                }
+                if(newurl && newurl .match(/http(s):\/\/.+/)) {
+                    this.editorcore.relayCmd('createlink', newurl);
+                }
+                this.editorcore.focus();
+            }
+        });
+    },
     /**
      * Protected method that will not generally be called directly. It triggers
      * a toolbar update by reading the markup state of the current selection in the editor.
      */
-    updateToolbar: function(){
+    updateToolbar: function(editor ,ev, sel){
 
         if(!this.editorcore.activated){
             this.editor.onFirstFocus(); // is this neeed?
@@ -28350,39 +33198,89 @@ Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simpl
 
         var btns = this.buttons; 
         var doc = this.editorcore.doc;
-        btns.get('bold').setActive(doc.queryCommandState('bold'));
-        btns.get('italic').setActive(doc.queryCommandState('italic'));
-        //btns.get('underline').setActive(doc.queryCommandState('underline'));
+        var hasToggle  = false;
+        btns.each(function(e) {
+            if (e.enableToggle && e.cmd) {
+                hasToggle = hasToggle  || (['align-left', 'align-right', 'align-center', 'image' , 'link', 'underline'].indexOf(e.btnid) < 0 && doc.queryCommandState(e.cmd));
+                e.setActive(doc.queryCommandState(e.cmd));
+            }
+        }, this);
         
-        btns.get('align-left').setActive(doc.queryCommandState('justifyleft'));
-        btns.get('align-center').setActive(doc.queryCommandState('justifycenter'));
-        btns.get('align-right').setActive(doc.queryCommandState('justifyright'));
         
-        //btns[frameId + '-insertorderedlist').setActive(doc.queryCommandState('insertorderedlist'));
-        btns.get('list').setActive(doc.queryCommandState('insertunorderedlist'));
-         /*
+        if (ev &&
+            (ev.type == 'mouseup' || ev.type == 'click' ) &&
+            ev.target && ev.target.tagName != 'BODY' ) { // && ev.target.tagName == 'IMG') {
+            // they have click on an image...
+            // let's see if we can change the selection...
+            sel = ev.target;
+            
+        }
         
         var ans = this.editorcore.getAllAncestors();
-        if (this.formatCombo) {
+        if (!sel) { 
+            sel = ans.length ? (ans[0] ?  ans[0]  : ans[1]) : this.editorcore.doc.body;
+            sel = sel ? sel : this.editorcore.doc.body;
+            sel = sel.tagName.length ? sel : this.editorcore.doc.body;
             
+        }
+        
+        var lastSel = this.selectedNode;
+        this.selectedNode = sel;
+         
+        // ok see if we are editing a block?
+        
+        var db = false;
+        // you are not actually selecting the block.
+        if (sel && sel.hasAttribute('data-block')) {
+            db = sel;
+        } else if (sel && sel.closest('[data-block]')) {
+            db = sel.closest('[data-block]');
+        }
+        
+        Array.from(this.editorcore.doc.body.querySelectorAll('.roo-ed-selection')).forEach(function(e) {
+            e.classList.remove('roo-ed-selection');
+        });
+        
+        var block = false;
+        if (db && this.editorcore.enableBlocks) {
+            block = Roo.htmleditor.Block.factory(db);
             
-            var store = this.formatCombo.store;
-            this.formatCombo.setValue("");
-            for (var i =0; i < ans.length;i++) {
-                if (ans[i] && store.query('tag',ans[i].tagName.toLowerCase(), false).length) {
-                    // select it..
-                    this.formatCombo.setValue(ans[i].tagName.toLowerCase());
-                    break;
-                }
+            if (block) {
+                db.className =  (db.classList.length > 0  ? db.className + ' ' : '') +
+                    ' roo-ed-selection';
+                sel = this.selectedNode = db;
             }
         }
         
+        // highlight the 'a'..
+        var tn = sel && sel.tagName.toUpperCase() || '';
+        if (!block && sel && tn != 'A') {
+            var asel = sel.closest('A');
+            if (asel) {
+                sel = asel;
+            }
+        }
+       
+        btns.get('link').setActive(tn == 'A' && this.selectedNode.hasAttribute('href'));
+        btns.get('image').setActive(tn == 'IMG' || this.editorcore.enableBlocks && tn == 'FIGURE');
+        btns.get('underline').setActive(tn == 'U' || sel.closest('u') ? true : false);
         
-        
-        // hides menus... - so this cant be on a menu...
-        Roo.bootstrap.MenuMgr.hideAll();
-        */
         Roo.bootstrap.menu.Manager.hideAll();
+         
+        
+        
+        
+        
+        // handle delete button..
+        if (hasToggle || (tn.length && tn == 'BODY')) {
+            this.deleteBtn.hide();
+            return;
+            
+        }
+        this.deleteBtn.show();
+        
+        
+        
         //this.editorsyncValue();
     },
     onFirstFocus: function() {
@@ -28390,6 +33288,49 @@ Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simpl
            item.enable();
         });
     },
+    
+    onDelete : function()
+    {
+        var range = this.editorcore.createRange();
+        var selection = this.editorcore.getSelection();
+        var sn = this.selectedNode;
+        range.setStart(sn,0);
+        range.setEnd(sn,0); 
+        
+        
+        if (sn.hasAttribute('data-block')) {
+            var block = Roo.htmleditor.Block.factory(this.selectedNode);
+            if (block) {
+                sn = block.removeNode();
+                sn.parentNode.removeChild(sn);
+                selection.removeAllRanges();
+                selection.addRange(range);
+                this.updateToolbar(null, null, null);
+                this.editorcore.fireEditorEvent(false);
+                return;
+            }   
+             
+        }
+        if (!sn) {
+            return; // should not really happen..
+        }
+        if (sn && sn.tagName == 'BODY') {
+            return;
+        }
+        var stn =  sn.childNodes[0] || sn.nextSibling || sn.previousSibling || sn.parentNode;
+        
+        // remove and keep parents.
+        a = new Roo.htmleditor.FilterKeepChildren({tag : false});
+        a.replaceTag(sn);
+        
+        selection.removeAllRanges();
+        selection.addRange(range);
+        this.editorcore.fireEditorEvent(false);
+        
+        
+    },
+    
+    
     toggleSourceEdit : function(sourceEditMode){
         
           
@@ -28775,8 +33716,14 @@ Roo.extend(Roo.bootstrap.PagingToolbar, Roo.bootstrap.nav.Simplebar, {
     },
 
     // private
-    onLoadError : function(){
+    onLoadError : function(proxy, o){
         this.loading.enable();
+        if (this.ds.events.loadexception.listeners.length  < 2) {
+            // nothing has been assigned to loadexception except this...
+            // so 
+            Roo.MessageBox.alert("Error loading",o.raw.errorMsg);
+
+        }
     },
 
     // private
@@ -29222,7 +34169,7 @@ Roo.extend(Roo.bootstrap.Graph, Roo.bootstrap.Component,  {
 });
 
  
-/*
+Roo.bootstrap.dash = {};/*
  * - LGPL
  *
  * numberBox
@@ -29882,10 +34829,18 @@ Roo.extend(Roo.bootstrap.Tooltip, Roo.bootstrap.Component,  {
         
         this.el.removeClass(['fade','top','bottom', 'left', 'right','in',
                              'bs-tooltip-top','bs-tooltip-bottom', 'bs-tooltip-left', 'bs-tooltip-right']);
+
+        if(this.bindEl.attr('tooltip-class')) {
+            this.el.addClass(this.bindEl.attr('tooltip-class'));
+        }
         
         var placement = typeof this.placement == 'function' ?
             this.placement.call(this, this.el, on_el) :
             this.placement;
+        
+        if(this.bindEl.attr('tooltip-placement')) {
+            placement = this.bindEl.attr('tooltip-placement');
+        }
             
         var autoToken = /\s?auto?\s?/i;
         var autoPlace = autoToken.test(placement);
@@ -29975,6 +34930,9 @@ Roo.extend(Roo.bootstrap.Tooltip, Roo.bootstrap.Component,  {
             return;
         }
         //this.el.setXY([0,0]);
+        if(this.bindEl.attr('tooltip-class')) {
+            this.el.removeClass(this.bindEl.attr('tooltip-class'));
+        }
         this.el.removeClass(['show', 'in']);
         //this.el.hide();
         
@@ -37843,6 +42801,8 @@ Roo.bootstrap.SplitBar.BOTTOM = 4;
  */
 Roo.bootstrap.layout.Manager = function(config)
 {
+    this.monitorWindowResize = true; // do this before we apply configuration.
+    
     Roo.bootstrap.layout.Manager.superclass.constructor.call(this,config);
 
 
@@ -37850,7 +42810,7 @@ Roo.bootstrap.layout.Manager = function(config)
 
 
     /** false to disable window resize monitoring @type Boolean */
-    this.monitorWindowResize = true;
+    
     this.regions = {};
     this.addEvents({
         /**
@@ -40502,7 +45462,7 @@ Roo.extend(Roo.bootstrap.panel.Content, Roo.bootstrap.Component, {
 
     ignoreResize : function(w, h)
     {
-        return false; // always resize?
+        //return false; // always resize?
         if(this.lastSize && this.lastSize.width == w && this.lastSize.height == h){
             return true;
         }else{
@@ -40909,9 +45869,7 @@ Roo.bootstrap.panel.Grid = function(config)
 
 Roo.extend(Roo.bootstrap.panel.Grid, Roo.bootstrap.panel.Content,
 {
-    // private
-    is_resizing : false,
-    
+  
     getId : function(){
         return this.grid.id;
     },
@@ -40926,12 +45884,8 @@ Roo.extend(Roo.bootstrap.panel.Grid, Roo.bootstrap.panel.Content,
     
     setSize : function(width, height)
     {
-        if (this.is_resizing) {
-            return;
-        
-        }
-        this.is_resizing = true;
-        if(!this.ignoreResize(width, height)){
+     
+        //if(!this.ignoreResize(width, height)){
             var grid = this.grid;
             var size = this.adjustForComponents(width, height);
             // tfoot is not a footer?
@@ -40959,8 +45913,8 @@ Roo.extend(Roo.bootstrap.panel.Grid, Roo.bootstrap.panel.Content,
             //}
              
             grid.autoSize();
-        }
-        this.is_resizing = false;
+        //}
+   
     },