X-Git-Url: http://git.roojs.org/?a=blobdiff_plain;f=roojs-debug.js;h=5d6e7b2ce04f904b59513ed2de828331aad6d182;hb=4661ff928a7659d86219009f55c18ba89fe23059;hp=ef18484e8c1ff5eaa5d18b08893ddb560543f166;hpb=37cee4aa5dd65c21bda2a50c3e96e898db1261a5;p=roojs1 diff --git a/roojs-debug.js b/roojs-debug.js index ef18484e8c..5d6e7b2ce0 100644 --- a/roojs-debug.js +++ b/roojs-debug.js @@ -954,6 +954,16 @@ String.prototype.unicodeClean = function () { ); }; + +/** + * Make the first letter of a string uppercase + * + * @return {String} The new string. + */ +String.prototype.toUpperCaseFirst = function () { + return this.charAt(0).toUpperCase() + this.slice(1); +}; + /* * Based on: * Ext JS Library 1.1.1 @@ -1392,17 +1402,17 @@ Date.createParser = function(format) { } code += "if (y >= 0 && m >= 0 && d > 0 && h >= 0 && i >= 0 && s >= 0)\n" - + "{v = new Date(y, m, d, h, i, s);}\n" + + "{v = new Date(y, m, d, h, i, s); v.setFullYear(y);}\n" + "else if (y >= 0 && m >= 0 && d > 0 && h >= 0 && i >= 0)\n" - + "{v = new Date(y, m, d, h, i);}\n" + + "{v = new Date(y, m, d, h, i); v.setFullYear(y);}\n" + "else if (y >= 0 && m >= 0 && d > 0 && h >= 0)\n" - + "{v = new Date(y, m, d, h);}\n" + + "{v = new Date(y, m, d, h); v.setFullYear(y);}\n" + "else if (y >= 0 && m >= 0 && d > 0)\n" - + "{v = new Date(y, m, d);}\n" + + "{v = new Date(y, m, d); v.setFullYear(y);}\n" + "else if (y >= 0 && m >= 0)\n" - + "{v = new Date(y, m);}\n" + + "{v = new Date(y, m); v.setFullYear(y);}\n" + "else if (y >= 0)\n" - + "{v = new Date(y);}\n" + + "{v = new Date(y); v.setFullYear(y);}\n" + "}return (v && (z || o))?\n" // favour UTC offset over GMT offset + " ((z)? v.add(Date.SECOND, (v.getTimezoneOffset() * 60) + (z*1)) :\n" // reset to UTC, then add offset + " v.add(Date.HOUR, (v.getGMTOffset() / 100) + (o / -100))) : v\n" // reset to GMT, then add offset @@ -1462,7 +1472,7 @@ Date.formatCodeToRegex = function(character, currentGroup) { s:"(\\d{1,2})"}; // Numeric representation of a month, without leading zeros case "m": return {g:1, - c:"m = parseInt(results[" + currentGroup + "], 10) - 1;\n", + c:"m = Math.max(0,parseInt(results[" + currentGroup + "], 10) - 1);\n", s:"(\\d{2})"}; // Numeric representation of a month, with leading zeros case "t": return {g:0, @@ -4897,7 +4907,468 @@ Roo.lib.Easing = { } }; })(); -/* +/** + * Originally based of this code... - refactored for Roo... + * https://github.com/aaalsaleh/undo-manager + + * undo-manager.js + * @author Abdulrahman Alsaleh + * @copyright 2015 Abdulrahman Alsaleh + * @license MIT License (c) + * + * Hackily modifyed by alan@roojs.com + * + * + * + * + * TOTALLY UNTESTED... + * + * Documentation to be done.... + */ + + +/** +* @class Roo.lib.UndoManager +* An undo manager implementation in JavaScript. It follows the W3C UndoManager and DOM Transaction +* Draft and the undocumented and disabled Mozilla Firefox's UndoManager implementation. + + * Usage: + *

+
+
+editor.undoManager = new Roo.lib.UndoManager(1000, editor);
+ 
+
+ +* For more information see this blog post with examples: +* DomHelper + - Create Elements using DOM, HTML fragments and Templates. +* @constructor +* @param {Number} limit how far back to go ... use 1000? +* @param {Object} scope usually use document.. +*/ + +Roo.lib.UndoManager = function (limit, undoScopeHost) +{ + this.stack = []; + this.limit = limit; + this.scope = undoScopeHost; + this.fireEvent = typeof CustomEvent != 'undefined' && undoScopeHost && undoScopeHost.dispatchEvent; + if (this.fireEvent) { + this.bindEvents(); + } + this.reset(); + +}; + +Roo.lib.UndoManager.prototype = { + + limit : false, + stack : false, + scope : false, + fireEvent : false, + position : 0, + length : 0, + + + /** + * To push and execute a transaction, the method undoManager.transact + * must be called by passing a transaction object as the first argument, and a merge + * flag as the second argument. A transaction object has the following properties: + * + * Usage: +

+undoManager.transact({
+    label: 'Typing',
+    execute: function() { ... },
+    undo: function() { ... },
+    // redo same as execute
+    redo: function() { this.execute(); }
+}, false);
+
+// merge transaction
+undoManager.transact({
+    label: 'Typing',
+    execute: function() { ... },  // this will be run...
+    undo: function() { ... }, // what to do when undo is run.
+    // redo same as execute
+    redo: function() { this.execute(); }
+}, true); 
+
+ * + * + * @param {Object} transaction The transaction to add to the stack. + * @return {String} The HTML fragment + */ + + + transact : function (transaction, merge) + { + if (arguments.length < 2) { + throw new TypeError('Not enough arguments to UndoManager.transact.'); + } + + transaction.execute(); + + this.stack.splice(0, this.position); + if (merge && this.length) { + this.stack[0].push(transaction); + } else { + this.stack.unshift([transaction]); + } + + this.position = 0; + + if (this.limit && this.stack.length > this.limit) { + this.length = this.stack.length = this.limit; + } else { + this.length = this.stack.length; + } + + if (this.fireEvent) { + this.scope.dispatchEvent( + new CustomEvent('DOMTransaction', { + detail: { + transactions: this.stack[0].slice() + }, + bubbles: true, + cancelable: false + }) + ); + } + + //Roo.log("transaction: pos:" + this.position + " len: " + this.length + " slen:" + this.stack.length); + + + }, + + undo : function () + { + //Roo.log("undo: pos:" + this.position + " len: " + this.length + " slen:" + this.stack.length); + + if (this.position < this.length) { + for (var i = this.stack[this.position].length - 1; i >= 0; i--) { + this.stack[this.position][i].undo(); + } + this.position++; + + if (this.fireEvent) { + this.scope.dispatchEvent( + new CustomEvent('undo', { + detail: { + transactions: this.stack[this.position - 1].slice() + }, + bubbles: true, + cancelable: false + }) + ); + } + } + }, + + redo : function () + { + if (this.position > 0) { + for (var i = 0, n = this.stack[this.position - 1].length; i < n; i++) { + this.stack[this.position - 1][i].redo(); + } + this.position--; + + if (this.fireEvent) { + this.scope.dispatchEvent( + new CustomEvent('redo', { + detail: { + transactions: this.stack[this.position].slice() + }, + bubbles: true, + cancelable: false + }) + ); + } + } + }, + + item : function (index) + { + if (index >= 0 && index < this.length) { + return this.stack[index].slice(); + } + return null; + }, + + clearUndo : function () { + this.stack.length = this.length = this.position; + }, + + clearRedo : function () { + this.stack.splice(0, this.position); + this.position = 0; + this.length = this.stack.length; + }, + /** + * Reset the undo - probaly done on load to clear all history. + */ + reset : function() + { + this.stack = []; + this.position = 0; + this.length = 0; + this.current_html = this.scope.innerHTML; + if (this.timer !== false) { + clearTimeout(this.timer); + } + this.timer = false; + this.merge = false; + this.addEvent(); + + }, + current_html : '', + timer : false, + merge : false, + + + // this will handle the undo/redo on the element.? + bindEvents : function() + { + var el = this.scope; + el.undoManager = this; + + + this.scope.addEventListener('keydown', function(e) { + if ((e.ctrlKey || e.metaKey) && e.keyCode === 90) { + if (e.shiftKey) { + el.undoManager.redo(); // Ctrl/Command + Shift + Z + } else { + el.undoManager.undo(); // Ctrl/Command + Z + } + + e.preventDefault(); + } + }); + /// ignore keyup.. + this.scope.addEventListener('keyup', function(e) { + if ((e.ctrlKey || e.metaKey) && e.keyCode === 90) { + e.preventDefault(); + } + }); + + + + var t = this; + + el.addEventListener('input', function(e) { + if(el.innerHTML == t.current_html) { + return; + } + // only record events every second. + if (t.timer !== false) { + clearTimeout(t.timer); + t.timer = false; + } + t.timer = setTimeout(function() { t.merge = false; }, 1000); + + t.addEvent(t.merge); + t.merge = true; // ignore changes happening every second.. + }); + }, + /** + * Manually add an event. + * Normall called without arguements - and it will just get added to the stack. + * + */ + + addEvent : function(merge) + { + //Roo.log("undomanager +" + (merge ? 'Y':'n')); + // not sure if this should clear the timer + merge = typeof(merge) == 'undefined' ? false : merge; + + this.scope.undoManager.transact({ + scope : this.scope, + oldHTML: this.current_html, + newHTML: this.scope.innerHTML, + // nothing to execute (content already changed when input is fired) + execute: function() { }, + undo: function() { + this.scope.innerHTML = this.current_html = this.oldHTML; + }, + redo: function() { + this.scope.innerHTML = this.current_html = this.newHTML; + } + }, false); //merge); + + this.merge = merge; + + this.current_html = this.scope.innerHTML; + } + + + + + + +}; +/** + * @class Roo.lib.Range + * @constructor + * This is a toolkit, normally used to copy features into a Dom Range element + * Roo.lib.Range.wrap(x); + * + * + * + */ +Roo.lib.Range = function() { }; + +/** + * Wrap a Dom Range object, to give it new features... + * @static + * @param {Range} the range to wrap + */ +Roo.lib.Range.wrap = function(r) { + return Roo.apply(r, Roo.lib.Range.prototype); +}; +/** + * find a parent node eg. LI / OL + * @param {string|Array} node name or array of nodenames + * @return {DomElement|false} + */ +Roo.apply(Roo.lib.Range.prototype, +{ + + closest : function(str) + { + if (typeof(str) != 'string') { + // assume it's a array. + for(var i = 0;i < str.length;i++) { + var r = this.closest(str[i]); + if (r !== false) { + return r; + } + + } + return false; + } + str = str.toLowerCase(); + var n = this.commonAncestorContainer; // might not be a node + while (n.nodeType != 1) { + n = n.parentNode; + } + + if (n.nodeName.toLowerCase() == str ) { + return n; + } + if (n.nodeName.toLowerCase() == 'body') { + return false; + } + + return n.closest(str) || false; + + }, + cloneRange : function() + { + return Roo.lib.Range.wrap(Range.prototype.cloneRange.call(this)); + } +});/** + * @class Roo.lib.Selection + * @constructor + * This is a toolkit, normally used to copy features into a Dom Selection element + * Roo.lib.Selection.wrap(x); + * + * + * + */ +Roo.lib.Selection = function() { }; + +/** + * Wrap a Dom Range object, to give it new features... + * @static + * @param {Range} the range to wrap + */ +Roo.lib.Selection.wrap = function(r, doc) { + Roo.apply(r, Roo.lib.Selection.prototype); + r.ownerDocument = doc; // usefull so we dont have to keep referening to it. + return r; +}; +/** + * find a parent node eg. LI / OL + * @param {string|Array} node name or array of nodenames + * @return {DomElement|false} + */ +Roo.apply(Roo.lib.Selection.prototype, +{ + /** + * the owner document + */ + ownerDocument : false, + + getRangeAt : function(n) + { + return Roo.lib.Range.wrap(Selection.prototype.getRangeAt.call(this,n)); + }, + + /** + * insert node at selection + * @param {DomElement|string} node + * @param {string} cursor (after|in|none) where to place the cursor after inserting. + */ + insertNode: function(node, cursor) + { + if (typeof(node) == 'string') { + node = this.ownerDocument.createElement(node); + if (cursor == 'in') { + node.innerHTML = ' '; + } + } + + var range = this.getRangeAt(0); + + if (this.type != 'Caret') { + range.deleteContents(); + } + var sn = node.childNodes[0]; // select the contents. + + + + range.insertNode(node); + if (cursor == 'after') { + node.insertAdjacentHTML('afterend', ' '); + sn = node.nextSibling; + } + + if (cursor == 'none') { + return; + } + + this.cursorText(sn); + }, + + cursorText : function(n) + { + + //var range = this.getRangeAt(0); + range = Roo.lib.Range.wrap(new Range()); + //range.selectNode(n); + + var ix = Array.from(n.parentNode.childNodes).indexOf(n); + range.setStart(n.parentNode,ix); + range.setEnd(n.parentNode,ix+1); + //range.collapse(false); + + this.removeAllRanges(); + this.addRange(range); + + Roo.log([n, range, this,this.baseOffset,this.extentOffset, this.type]); + }, + cursorAfter : function(n) + { + if (!n.nextSibling || n.nextSibling.nodeValue != ' ') { + n.insertAdjacentHTML('afterend', ' '); + } + this.cursorText (n.nextSibling); + } + + +});/* * Based on: * Ext JS Library 1.1.1 * Copyright(c) 2006-2007, Ext JS, LLC. @@ -5165,10 +5636,15 @@ Roo.DomHelper = function(){ from.data = to.data; return; } - + if (!from.parentNode) { + // not sure why this is happening? + return; + } // assume 'to' doesnt have '1/3 nodetypes! + // not sure why, by from, parent node might not exist? if (from.nodeType !=1 || from.tagName != to.tagName) { Roo.log(["ReplaceChild" , from, to ]); + from.parentNode.replaceChild(to, from); return; } @@ -5179,15 +5655,21 @@ Roo.DomHelper = function(){ continue; } if (ar[i].name == 'id') { // always keep ids? - continue; + continue; } + //if (ar[i].name == 'style') { + // throw "style removed?"; + //} + Roo.log("removeAttribute" + ar[i].name); from.removeAttribute(ar[i].name); } ar = to.attributes; for(var i = 0; i< ar.length;i++) { if (from.getAttribute(ar[i].name) == to.getAttribute(ar[i].name)) { + Roo.log("skipAttribute " + ar[i].name + '=' + to.getAttribute(ar[i].name)); continue; } + Roo.log("updateAttribute " + ar[i].name + '=>' + to.getAttribute(ar[i].name)); from.setAttribute(ar[i].name, to.getAttribute(ar[i].name)); } // children @@ -34289,6 +34771,7 @@ Roo.extend(Roo.LayoutDialog, Roo.BasicDialog, { /** * @class Roo.MessageBox + * @static * Utility class for generating different styles of message boxes. The alias Roo.Msg can also be used. * Example usage: *

@@ -39507,7 +39990,7 @@ Roo.extend(Roo.menu.Item, Roo.menu.BaseItem, {
      */
     text: '',
      /**
-     * @cfg {String} HTML to render in menu
+     * @cfg {String} html to render in menu
      * The text to show on the menu item (HTML version).
      */
     html: '',
@@ -41580,6 +42063,16 @@ Roo.extend(Roo.form.DateField, Roo.form.TriggerField,  {
      * The tooltip text to display when the date falls on a disabled date (defaults to 'Disabled')
      */
     disabledDatesText : "Disabled",
+	
+	
+	/**
+     * @cfg {Date/String} zeroValue
+     * if the date is less that this number, then the field is rendered as empty
+     * default is 1800
+     */
+	zeroValue : '1800-01-01',
+	
+	
     /**
      * @cfg {Date/String} minValue
      * The minimum allowed date. Can be either a Javascript date object or a string date in a
@@ -41756,6 +42249,15 @@ dateField.setValue('2006-5-4');
 
     // private
     parseDate : function(value){
+		
+		if (value instanceof Date) {
+			if (value < Date.parseDate(this.zeroValue, 'Y-m-d') ) {
+				return  '';
+			}
+			return value;
+		}
+		
+		
         if(!value || value instanceof Date){
             return value;
         }
@@ -41771,6 +42273,9 @@ dateField.setValue('2006-5-4');
                 v = Date.parseDate(value, this.altFormatsArray[i]);
             }
         }
+		if (v < Date.parseDate(this.zeroValue, 'Y-m-d') ) {
+			v = '';
+		}
         return v;
     },
 
@@ -45684,7 +46189,7 @@ Roo.extend(Roo.htmleditor.FilterStyleToTag, Roo.htmleditor.Filter,
         var cn = Array.from(node.childNodes);
         var nn = node;
         Roo.each(inject, function(t) {
-            var nc = node.ownerDocument.createelement(t);
+            var nc = node.ownerDocument.createElement(t);
             nn.appendChild(nc);
             nn = nc;
         });
@@ -45764,6 +46269,42 @@ Roo.extend(Roo.htmleditor.FilterLongBr, Roo.htmleditor.Filter,
 
     }
     
+}); 
+
+/**
+ * @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 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 = ' ';
+        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;
         
+    
         
         
-        if (node.tagName == 'BODY') {
-            
-            return this.cn(node, '');
+         
+    }
+};
+     
+/**
+ * @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, or style for using next param as style)
+     * @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);
         }
-             
-             // Prints the node tagName, such as , , etc
-        var ret = "<" + node.tagName +  this.attr(node) ;
-        
-        // elements with no children..
-        if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(node.tagName) > -1) {
-                return ret + '/>';
+        if (!n) {
+            return '';
+        }
+        if (attr == 'html') {
+            return n.innerHTML;
+        }
+        if (attr == 'style') {
+            return n.style[style]; 
         }
-        ret += '>';
         
+        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 = {};
         
-        var cindent = indent === false ? '' : (indent + '  ');
-        // tags where we will not pad the children.. (inline text tags etc..)
-        if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN', 'B', 'I', 'S'].indexOf(node.tagName) > -1) { // or code?
-            cindent = false;
+        return [
+             {
+                xtype : 'TextItem',
+                text : "Source: ",
+                xns : rooui.Toolbar  //Boostrap?
+            },
+            {
+                xtype : 'TextField',
+                allowBlank : false,
+                width : 150,
+                name : 'image_src',
+                listeners : {
+                    keyup : function (combo, e)
+                    { 
+                        toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+                        var b = block();
+                        b.image_src = this.getValue();
+                        b.updateElement();
+                        syncValue();
+                        toolbar.editorcore.onEditorEvent();
+                    }
+                },
+                xns : rooui.form
+                
+            },
+            {
+                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 : [
+                        ['auto'],
+                        ['50%'],
+                        ['100%']
+                    ],
+                    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.toggle(v == 'block' ? false : true);
+                },
+                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 == '50%' && this.align == 'center' ? '0 auto' : 0; 
+        
+        var img =   {
+            tag : 'img',
+            contenteditable : 'false',
+            src : this.image_src,
+            alt : d.innerText.replace(/\n/g, " "), // removeHTML..
+            style: {
+                width : 'auto',
+                'max-width': '100%',
+                margin : '0px' 
+                
+                
+            }
+        };
+        /*
+        '',
+        */
+                
+        if (this.href.length > 0) {
+            img = {
+                tag : 'a',
+                href: this.href,
+                contenteditable : 'true',
+                cn : [
+                    img
+                ]
+            };
         }
         
-        var cn = this.cn(node, cindent );
         
-        return ret + cn  + '';
+        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
+                ]
+            };
+        }
+        
+        return  {
+            tag: 'figure',
+            'data-block' : 'Figure',
+            contenteditable : 'false',
+            style : {
+                display: 'block',
+                float :  this.align ,
+                'max-width':  this.width,
+                width : 'auto',
+                margin:  m,
+                padding: '10px'
+                
+            },
+           
+            
+            align : this.align,
+            cn : [
+                img,
+              
+                {
+                    tag: 'figcaption',
+                    contenteditable : true,
+                    style : {
+                        'text-align': 'left',
+                        'margin-top' : '16px',
+                        'font-size' : '16px',
+                        'line-height' : '24px',
+                        'font-style': 'italic',
+                        display : this.caption_display
+                    },
+                    cls : this.cls.length > 0 ? (this.cls  + '-thumbnail' ) : '',
+                    html : this.caption
+                    
+                }
+            ]
+        };
+         
+    },
+    
+    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');
+        this.caption = this.getVal(node, 'figcaption', 'html');
+        //this.text_align = this.getVal(node, 'figcaption', 'style','text-align');
+        this.width = this.getVal(node, 'figure', 'style', 'max-width');
+        //this.margin = this.getVal(node, 'figure', 'style', 'margin');
         
     },
-    cn: function(node, indent)
+    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 ret = [];
         
-        var ar = Array.from(node.childNodes);
-        for (var i = 0 ; i < ar.length ; i++) {
+        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
+            } 
             
             
             
-            if (indent !== false   // indent==false preservies everything
-                && i > 0
-                && ar[i].nodeType == 3 
-                && ar[i].nodeValue.length > 0
-                && ar[i].nodeValue.match(/^\s+/)
-            ) {
-                if (ret.length && ret[ret.length-1] == "\n" + indent) {
-                    ret.pop(); // remove line break from last?
+        ];
+        
+    },
+    
+    
+  /**
+     * 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 ;
                 }
                 
-                ret.push(" "); // add a space if i'm a text item with a space at the front, as tidy will strip spaces.
-            }
-            if (indent !== false
-                && ar[i].nodeType == 1 // element - and indent is not set... 
-            ) {
-                ret.push("\n" + indent); 
-            }
+                
+                // widths ?
+                tr.cn.push(td);
+                    
+                
+            }, this);
+            ncols = Math.max(nc, ncols);
             
-            ret.push(this.tidy(ar[i], indent));
-            // text + trailing indent 
-            if (indent !== false
-                && ar[i].nodeType == 3
-                && ar[i].nodeValue.length > 0
-                && ar[i].nodeValue.match(/\s+$/)
-            ){
-                ret.push("\n" + indent); 
-            }
             
+        }, 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;
         }
-        // what if all text?
         
+        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--;
         
-        return ret.join('');
+    },
+    removeColumn : function()
+    {
+        this.deleteColumn({
+            type: 'col',
+            col : this.no_col-1
+        });
+        this.updateElement();
     },
     
-         
+     
+    addColumn : function()
+    {
         
-    attr : function(node)
+        this.rows.forEach(function(row) {
+            row.push(this.emptyCell());
+           
+        }, this);
+        this.updateElement();
+    },
+    
+    deleteRow : function(sel)
     {
-        var attr = [];
-        for(i = 0; i < node.attributes.length;i++) {
+        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.
+            }
             
-            // skip empty values?
-            if (!node.attributes.item(i).value.length) {
-                continue;
+        }, 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);
             }
-            attr.push(  node.attributes.item(i).name + '="' +
-                    Roo.util.Format.htmlEncode(node.attributes.item(i).value) + '"'
-            );
+        });
+        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());
+           
         }
-        return attr.length ? (' ' + attr.join(' ') ) : '';
+        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);
+        });
     }
     
     
     
-}
+    
+})
+
 /**
- * @class Roo.htmleditor.KeyEnter
- * Handle Enter press..
- * @cfg {Roo.HtmlEditorCore} core the editor.
+ *
+ * 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.KeyEnter = function(cfg) {
+Roo.htmleditor.BlockTd = function(cfg)
+{
+    if (cfg.node) {
+        this.readElement(cfg.node);
+        this.updateElement(cfg.node);
+    }
     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.prototype = {
+Roo.extend(Roo.htmleditor.BlockTd, Roo.htmleditor.Block, {
+ 
+    node : false,
     
-    core : false,
+    width: '',
+    textAlign : 'left',
+    valign : 'top',
     
-    keypress : function(e) {
-        if (e.charCode != 13) {
-            return true;
+    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;
+        
+        
+    },
+     
+    // the default cell object... at present...
+    emptyCell : function() {
+        return {
+            colspan :  1,
+            rowspan :  1,
+            textAlign : 'left',
+            html : " " // 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') {
+                    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.
         }
-        e.preventDefault();
-        // https://stackoverflow.com/questions/18552336/prevent-contenteditable-adding-div-on-enter-chrome
-        var doc = this.core.doc;
         
-        var docFragment = doc.createDocumentFragment();
+        
+        return ret;
+        
+    },
+    
     
-        //add a new line
-        var newEle = doc.createTextNode('\n');
-        docFragment.appendChild(newEle);
     
     
-        var range = this.core.win.getSelection().getRangeAt(0);
-        var n = range.commonAncestorContainer ;
-        while (n && n.nodeType != 1) {
-            n  = n.parentNode;
+    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 li = false;
-        if (n && n.tagName == 'UL') {
-            li = doc.createElement('LI');
-            n.appendChild(li);
-            
+        var table = this.toTableArray();
+        
+        if (typeof(table[this.cellData.row][this.cellData.col+this.cellData.colspan]) == 'undefined') {
+            return; // nothing right?
         }
-        if (n && n.tagName == 'LI') {
-            li = doc.createElement('LI');
-            if (n.nextSibling) {
-                n.parentNode.insertBefore(li, n.firstSibling);
-                
-            } else {
-                n.parentNode.appendChild(li);
-            }
+        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.
         }
-        if (li) {   
-            range = doc.createRange();
-            range.setStartAfter(li);
-            range.collapse(true);
         
-            //make the cursor there
-            var sel = this.core.win.getSelection();
-            sel.removeAllRanges();
-            sel.addRange(range);
-            return false;
-            
-            
-        }
-        //add the br, or p, or something else
-        newEle = doc.createElement('br');
-        docFragment.appendChild(newEle);
-    
-        //make the br replace selection
         
-        range.deleteContents();
         
-        range.insertNode(docFragment);
+        this.node.innerHTML += ' ' + rc.cell.innerHTML;
+        tr.removeChild(rc.cell);
+        this.colspan += rc.colspan;
+        this.node.setAttribute('colspan', this.colspan);
+
+    },
     
-        //create a new range
-        range = doc.createRange();
-        range.setStartAfter(newEle);
-        range.collapse(true);
     
-        //make the cursor there
-        var sel = this.core.win.getSelection();
-        sel.removeAllRanges();
-        sel.addRange(range);
+    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);
+    },
     
-        return false;
+    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');
+                    continue;
+                }
+                 
+                var ntd = this.node.cloneNode(); // which col/row should be 0..
+                ntd.removeAttribute('id'); //
+                //ntd.style.width  = '';
+                ntd.innerHTML = '';
+                table[r][c] = { cell : ntd, col : c, row: r , colspan : 1 , rowspan : 1   };
+            }
+            
+        }
+        this.redrawAllCells(table);
+        
          
-    }
-};
-     
-/**
- * @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.
-}
-
-Roo.htmleditor.Block.factory = function(node)
-{
-    
-    var id = Roo.get(node).id;
-    if (typeof(Roo.htmleditor.Block.cache[id]) != 'undefined') {
-        Roo.htmleditor.Block.cache[id].readElement();
-        return Roo.htmleditor.Block.cache[id];
-    }
-    
-    var cls = Roo.htmleditor['Block' + Roo.get(node).attr('data-block')];
-    if (typeof(cls) == 'undefined') {
-        Roo.log("OOps missing block : " + 'Block' + Roo.get(node).attr('data-block'));
-        return false;
-    }
-    Roo.htmleditor.Block.cache[id] = new cls({ node: node });
-    return Roo.htmleditor.Block.cache[id];  /// should trigger update element
-};
-// 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 : 'Image with caption',
     
-    context : false,
-    /**
-     * Update a node with values from this object
-     * @param {DomElement} node
-     */
-    updateElement : function(node)
+    redrawAllCells: function(table)
     {
-        Roo.DomHelper.update(node === undefined ? this.node : node, this.toObject());
+        
+         
+        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;
+            }
+        }
+        
     },
-     /**
-     * convert to plain HTML for calling insertAtCursor..
-     */
-    toHTML : function()
+    updateWidths : function(table)
     {
-        return Roo.DomHelper.markup(this.toObject());
+        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);
+                }
+                table[r][c].cell = false; // done
+            }
+        }
     },
-    /**
-     * 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, or style for using next param as style)
-     * @param {String} style the style property - eg. text-align
-     */
-    getVal : function(node, tag, attr, style)
+    normalizeWidths : function(table)
     {
-        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 (attr == 'html') {
-            return n.innerHTML;
-        }
-        if (attr == 'style') {
-            return Roo.get(n).getStyle(style);
+    
+        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 = [];
         
-        return Roo.get(n).attr(attr);
+        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..
+        
+    
     },
-    /**
-     * create a DomHelper friendly object - for use with 
-     * Roo.DomHelper.markup / overwrite / etc..
-     * (override this)
-     */
-    toObject : function()
+    
+    shrinkColumn : function()
     {
-        return {};
+        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);
+         
     },
-      /**
-     * Read a node that has a 'data-block' property - and extract the values from it.
-     * @param {DomElement} node - the node
-     */
-    readElement : function(node)
+    growColumn : function()
     {
-        
-    } 
-    
-    
-};
-
- 
-
-/**
- * @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} text_align (left|right) alignment for the text caption default left.
- * @cfg {String} caption the text to appear below  (and in the alt tag)
- * @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: 'left',
-    caption : '',
-    text_align: 'left',
-    
-    width : '46%',
-    margin: '2%',
-    
-    // used by context menu
-    friendly_name : 'Image with caption',
-    
-    context : { // ?? static really
-        width : {
-            title: "Width",
-            width: 40
-            // ?? number
-        },
-        margin : {
-            title: "Margin",
-            width: 40
-            // ?? number
-        },
-        align: {
-            title: "Align",
-            opts : [[ "left"],[ "right"]],
-            width : 80
-            
-        },
-        text_align: {
-            title: "Caption Align",
-            opts : [ [ "left"],[ "right"],[ "center"]],
-            width : 80
-        },
-        
-       
-        image_src : {
-            title: "Src",
-            width: 220
+        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);
+         
     },
-    /**
-     * create a DomHelper friendly object - for use with
-     * Roo.DomHelper.markup / overwrite / etc..
-     */
-    toObject : function()
+    deleteRow : function()
     {
-        var d = document.createElement('div');
-        d.innerHTML = this.caption;
+        // 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);
         
-        return {
-            tag: 'figure',
-            'data-block' : 'Figure',
-            contenteditable : 'false',
-            style : {
-                display: 'table',
-                float :  this.align ,
-                width :  this.width,
-                margin:  this.margin
-            },
-            cn : [
-                {
-                    tag : 'img',
-                    src : this.image_src,
-                    alt : d.innerText.replace(/\n/g, " "), // removeHTML..
-                    style: {
-                        width: '100%'
-                    }
-                },
-                {
-                    tag: 'figcaption',
-                    contenteditable : true,
-                    style : {
-                        'text-align': this.text_align
-                    },
-                    html : this.caption
-                    
-                }
-            ]
-        };
     },
-    
-    readElement : function(node)
+    deleteColumn : function()
     {
-        this.image_src = this.getVal(node, 'img', 'src');
-        this.align = this.getVal(node, 'figure', 'style', 'float');
-        this.caption = this.getVal(node, 'figcaption', 'html');
-        this.text_align = this.getVal(node, 'figcaption', 'style','text-align');
-        this.width = this.getVal(node, 'figure', 'style', 'width');
-        this.margin = this.getVal(node, 'figure', 'style', 'margin');
+        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);
+    }
     
     
     
@@ -46318,7 +48199,8 @@ Roo.HtmlEditorCore = function(config){
          * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
          * @param {Roo.HtmlEditorCore} this
          */
-        editorevent: true
+        editorevent: true 
+         
         
     });
     
@@ -46354,15 +48236,30 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
      * @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.
+     * @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..
@@ -46386,6 +48283,8 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
      
     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
@@ -46490,6 +48389,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
                 if(this.doc.body || this.doc.readyState == 'complete'){
                     try {
                         this.doc.designMode="on";
+                        
                     } catch (e) {
                         return;
                     }
@@ -46577,28 +48477,26 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
      */
     syncValue : function()
     {
-        Roo.log("HtmlEditorCore:syncValue (EDITOR->TEXT)");
+        //Roo.log("HtmlEditorCore:syncValue (EDITOR->TEXT)");
         if(this.initialized){
-            var bd = (this.doc.body || this.doc.documentElement);
-            //this.cleanUpPaste(); -- this is done else where and causes havoc..
-            
-            // not sure if this is really the place for this
-            // the blocks are synced occasionaly - since we currently dont add listeners on the blocks
-            // this has to update attributes that get duped.. like alt and caption..
             
+            this.undoManager.addEvent();
+
             
-            //Roo.each(Roo.get(this.doc.body).query('*[data-block]'), function(e) {
-            //     Roo.htmleditor.Block.factory(e);
-            //},this);
+            var bd = (this.doc.body || this.doc.documentElement);
+           
             
             
             var div = document.createElement('div');
             div.innerHTML = bd.innerHTML;
-            // remove content editable. (blocks)
-            
+             
            
-            new Roo.htmleditor.FilterAttributes({node : div, attrib_black: [ 'contenteditable' ] });
+            if (this.enableBlocks) {
+                new Roo.htmleditor.FilterBlock({ node : div });
+            }
             //?? tidy?
+            
+            
             var html = div.innerHTML;
             if(Roo.isSafari){
                 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
@@ -46650,7 +48548,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
      */
     pushValue : function()
     {
-        Roo.log("HtmlEditorCore:pushValue (TEXT->EDITOR)");
+        //Roo.log("HtmlEditorCore:pushValue (TEXT->EDITOR)");
         if(this.initialized){
             var v = this.el.dom.value.trim();
             
@@ -46662,12 +48560,14 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
                 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
+            }
+            
+            Roo.htmleditor.Block.initAll(this.doc.body);
+            this.updateLanguage();
             
-            Roo.each(Roo.get(this.doc.body).query('*[data-block]'), function(e) {
-                
-                Roo.htmleditor.Block.factory(e);
-                
-            },this);
             var lc = this.doc.body.lastChild;
             if (lc && lc.nodeType == 1 && lc.getAttribute("contenteditable") == "false") {
                 // add an extra line at the end.
@@ -46738,7 +48638,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
         dbody.bgProperties = 'fixed'; // ie
         //Roo.DomHelper.applyStyles(dbody, ss);
         Roo.EventManager.on(this.doc, {
-            //'mousedown': this.onEditorEvent,
+             
             'mouseup': this.onEditorEvent,
             'dblclick': this.onEditorEvent,
             'click': this.onEditorEvent,
@@ -46754,6 +48654,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
         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);
         }
@@ -46768,7 +48669,8 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
         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..
@@ -46790,7 +48692,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
         
         var html = cd.getData('text/html'); // clipboard event
         var parser = new Roo.rtf.Parser(cd.getData('text/rtf'));
-        var images = parser.doc.getElementsByType('pict');
+        var images = parser.doc ? parser.doc.getElementsByType('pict') : [];
         Roo.log(images);
         //Roo.log(imgs);
         // fixme..
@@ -46802,29 +48704,58 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
         
         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) {
             Roo.each(d.getElementsByTagName('img'), function(img, i) {
                 img.setAttribute('src', images[i]);
             });
         }
-        
-      
-        new Roo.htmleditor.FilterStyleToTag({ node : d });
-        new Roo.htmleditor.FilterAttributes({
-            node : d,
-            attrib_white : ['href', 'src', 'name', 'align'],
-            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 });
+        if (this.autoClean) {
+            new Roo.htmleditor.FilterStyleToTag({ node : d });
+            new Roo.htmleditor.FilterAttributes({
+                node : d,
+                attrib_white : ['href', 'src', 'name', 'align'],
+                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 });
+        }
+        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(/ /g,' '));
+        if (this.enableBlocks) {
+            Roo.htmleditor.Block.initAll(this.doc.body);
+        }
         
-        this.insertAtCursor(d.innerHTML);
         
         e.preventDefault();
         return false;
@@ -46853,7 +48784,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;
          
@@ -46898,10 +48829,48 @@ 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..)
+        }
+        // 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 
- 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) { @@ -46923,7 +48892,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { } this.execCmd("formatblock", tg); - + this.undoManager.addEvent(); }, insertText : function(txt) @@ -46935,6 +48904,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { //alert(Sender.getAttribute('label')); range.insertNode(this.doc.createTextNode(txt)); + this.undoManager.addEvent(); } , @@ -46945,7 +48915,37 @@ 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': + // 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); @@ -46995,16 +48995,24 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { 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(); @@ -47035,9 +49043,11 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { } if(cmd){ - this.win.focus(); - this.execCmd(cmd); - this.deferFocus(); + + this.relayCmd(cmd); + //this.win.focus(); + //this.execCmd(cmd); + //this.deferFocus(); e.preventDefault(); } @@ -47047,6 +49057,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; @@ -47060,7 +49072,8 @@ 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){ @@ -47073,6 +49086,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { } } } + */ //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste // this.cleanUpPaste.defer(100, this); // return; @@ -47089,6 +49103,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { this.execCmd('InsertHTML','    '); this.deferFocus(); } + //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste // this.cleanUpPaste.defer(100, this); // return; @@ -47105,6 +49120,8 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { this.deferFocus(); return; } + this.mozKeyPress(e); + //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste // this.cleanUpPaste.defer(100, this); // return; @@ -47138,25 +49155,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) + selectNode : function(node, collapse) { - - var nodeRange = node.ownerDocument.createRange(); - try { - nodeRange.selectNode(node); - } catch (e) { - nodeRange.selectNodeContents(node); - } - //nodeRange.collapse(true); - var s = this.win.getSelection(); - s.removeAllRanges(); - s.addRange(nodeRange); + 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() @@ -47165,8 +49184,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { // should we cache this!!!! - - + var range = this.createRange(this.getSelection()).cloneRange(); @@ -47230,6 +49248,8 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { return nodes[0]; }, + + createRange: function(sel) { // this has strange effects when using with @@ -47541,6 +49561,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; @@ -47763,12 +49793,26 @@ Roo.extend(Roo.form.HtmlEditor, Roo.form.Field, { */ allowComments: false, /** - * @cfg {string} bodyCls- default '' default classes to add to body of editable area - usually undoreset is a good start.. + * @cfg {boolean} enableBlocks - default true - if the block editor (table and figure should be enabled) */ + enableBlocks : true, + /** + * @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 {string} bodyCls- default '' default classes to add to body of editable area - usually undoreset is a good start.. + */ + bodyCls : '', + /** + * @cfg {String} language default en - language of text (usefull for rtl languages) + * + */ + language: 'en', - bodyCls : '', - + // id of frame.. frameId: false, @@ -47910,8 +49954,19 @@ Roo.extend(Roo.form.HtmlEditor, Roo.form.Field, { }, - - + /** + * get the Context selected node + * @returns {DomElement|boolean} selected node if active or false if none + * + */ + getSelectedNode : function() + { + if (this.toolbars.length < 2 || !this.toolbars[1].tb) { + return false; + } + return this.toolbars[1].tb.selectedNode; + + }, // private onRender : function(ct, position) { @@ -48201,7 +50256,17 @@ Roo.extend(Roo.form.HtmlEditor, Roo.form.Field, { this.editorcore.pushValue(); }, + /** + * update the language in the body - really done by core + * @param {String} language - eg. en / ar / zh-CN etc.. + */ + updateLanguage : function(lang) + { + this.language = lang; + this.editorcore.language = lang; + this.editorcore.updateLanguage(); + }, // private deferFocus : function(){ this.focus.defer(10, this); @@ -48834,10 +50899,13 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarStandard.prototype, { // private used internally createLink : function(){ Roo.log("create link?"); - var url = prompt(this.createLinkText, this.defaultLinkValue); - if(url && url != 'http:/'+'/'){ - this.editorcore.relayCmd('createlink', url); - } + var ec = this.editorcore; + Roo.MessageBox.prompt("Add Link URL",this.createLinkText, function(url) { + if(url && url != 'http:/'+'/'){ + ec.relayCmd('createlink', url); + } + }); + }, @@ -48950,6 +51018,11 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarStandard.prototype, { this.tb.items.each(function(item){ item.enable(); }); + // initialize 'blocks' + Roo.each(Roo.get(this.editorcore.doc.body).query('*[data-block]'), function(e) { + Roo.htmleditor.Block.factory(e).updateElement(e); + },this); + } } @@ -49341,9 +51414,9 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, { // disable everything... var ty= Roo.form.HtmlEditor.ToolbarContext.types; this.toolbars = {}; - + // block toolbars are built in updateToolbar when needed. for (var i in ty) { - + this.toolbars[i] = this.buildToolbar(ty[i],i); } this.tb = this.toolbars.BODY; @@ -49384,7 +51457,7 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, { this.editor.onFirstFocus(); return; } - Roo.log(ev ? ev.target : 'NOTARGET'); + //Roo.log(ev ? ev.target : 'NOTARGET'); // http://developer.yahoo.com/yui/docs/simple-editor.js.html @@ -49394,7 +51467,7 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, { if (ev && (ev.type == 'mouseup' || ev.type == 'click' ) && - ev.target && ev.target != 'BODY' ) { // && ev.target.tagName == 'IMG') { + 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; @@ -49402,8 +51475,14 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, { // this triggers looping? //this.editorcore.selectNode(sel); - } + } + // this forces an id.. + Array.from(this.editorcore.doc.body.querySelectorAll('.roo-ed-selection')).forEach(function(e) { + e.classList.remove('roo-ed-selection'); + }); + //Roo.select('.roo-ed-selection', false, this.editorcore.doc).removeClass('roo-ed-selection'); + //Roo.get(node).addClass('roo-ed-selection'); //var updateFooter = sel ? false : true; @@ -49426,25 +51505,33 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, { var left_label = tn; // ok see if we are editing a block? - var sel_el = Roo.get(sel); + var db = false; // you are not actually selecting the block. if (sel && sel.hasAttribute('data-block')) { db = sel; - } else if (sel && !sel.hasAttribute('contenteditable')) { - db = sel_el.findParent('[data-block]'); - var cepar = sel_el.findParent('[contenteditable=true]'); - if (db && cepar && cepar.tagName != 'BODY') { - db = false; // we are inside an editable block.. = not sure how we are going to handle nested blocks!? - } + } else if (sel && sel.closest('[data-block]')) { + + db = sel.closest('[data-block]'); + //var cepar = sel.closest('[contenteditable=true]'); + //if (db && cepar && cepar.tagName != 'BODY') { + // db = false; // we are inside an editable block.. = not sure how we are going to handle nested blocks!? + //} } var block = false; //if (db && !sel.hasAttribute('contenteditable') && sel.getAttribute('contenteditable') != 'true' ) { - if (db) { + if (db && this.editorcore.enableBlocks) { block = Roo.htmleditor.Block.factory(db); + + if (block) { + db.className = ( + db.classList.length > 0 ? db.className + ' ' : '' + ) + 'roo-ed-selection'; + + // since we removed it earlier... its not there.. tn = 'BLOCK.' + db.getAttribute('data-block'); //this.editorcore.selectNode(db); @@ -49477,7 +51564,7 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, { // update attributes - if (block) { + if (block && this.tb.fields) { this.tb.fields.each(function(e) { e.setValue(block[e.name]); @@ -49606,7 +51693,7 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, { var tb = new Roo.Toolbar(wdiv); - this.tb = tb; + ///this.tb = tb; // << this sets the active toolbar.. if (tlist === false && block) { tlist = block.contextMenu(this); } @@ -49653,7 +51740,7 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, { var tbc = Roo.form.HtmlEditor.ToolbarContext; - for (var i in tlist) { + for (var i = 0; i < tlist.length; i++) { // newer versions will use xtype cfg to create menus. if (typeof(tlist[i].xtype) != 'undefined') { @@ -49683,15 +51770,15 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, { fields: ['val', 'display'], data : opts }), - name : '-roo-edit-' + i, + name : '-roo-edit-' + tlist[i].name, - attrname : i, + attrname : tlist[i].name, stylename : item.style ? item.style : false, displayField: item.displayField ? item.displayField : 'val', valueField : 'val', typeAhead: false, - mode: typeof(tbc.stores[i]) != 'undefined' ? 'remote' : 'local', + mode: typeof(tbc.stores[tlist[i].name]) != 'undefined' ? 'remote' : 'local', editable : false, triggerAction: 'all', emptyText:'Select', @@ -49699,13 +51786,7 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, { width: item.width ? item.width : 130, listeners : { 'select': function(c, r, i) { - if (tb.selectedNode.hasAttribute('data-block')) { - var b = Roo.htmleditor.Block.factory(tb.selectedNode); - b[c.attrname] = r.get('val'); - b.updateElement(tb.selectedNode); - editorcore.syncValue(); - return; - } + if (c.stylename) { tb.selectedNode.style[c.stylename] = r.get('val'); @@ -49737,8 +51818,8 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, { */ } tb.addField( new Roo.form.TextField({ - name: '-roo-edit-' + i, - attrname : i, + name: '-roo-edit-' + tlist[i].name, + attrname : tlist[i].name, width: item.width, //allowBlank:true, @@ -49746,14 +51827,7 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, { listeners: { 'change' : function(f, nv, ov) { - if (tb.selectedNode.hasAttribute('data-block')) { - var b = Roo.htmleditor.Block.factory(tb.selectedNode); - b[f.attrname] = nv; - b.updateElement(tb.selectedNode); - editorcore.syncValue(); - return; - } - + tb.selectedNode.setAttribute(f.attrname, nv); editorcore.syncValue(); } @@ -49763,8 +51837,9 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, { } var _this = this; - + var show_delete = !block || block.deleteTitle !== false; if(nm == 'BODY'){ + show_delete = false; tb.addSeparator(); tb.addButton( { @@ -49780,51 +51855,54 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, { } tb.addFill(); - tb.addButton({ - text: 'Remove Block or Formating', // remove the tag, and puts the children outside... - - listeners : { - click : function () - { - // remove - // undo does not work. - var sn = tb.selectedNode; - if (!sn) { - return; - } - var stn = sn.childNodes[0] || sn.nextSibling || sn.previousSibling || sn.parentNode; - if (sn.hasAttribute('data-block')) { - stn = sn.nextSibling || sn.previousSibling || sn.parentNode; - sn.parentNode.removeChild(sn); + if (show_delete) { + tb.addButton({ + text: block && block.deleteTitle ? block.deleteTitle : 'Remove Block or Formating', // remove the tag, and puts the children outside... + + listeners : { + click : function () + { + var sn = tb.selectedNode; + if (block) { + sn = Roo.htmleditor.Block.factory(tb.selectedNode).removeNode(); + + } + if (!sn) { + return; + } + var stn = sn.childNodes[0] || sn.nextSibling || sn.previousSibling || sn.parentNode; + if (sn.hasAttribute('data-block')) { + stn = sn.nextSibling || sn.previousSibling || sn.parentNode; + sn.parentNode.removeChild(sn); + + } else if (sn && sn.tagName != 'BODY') { + // remove and keep parents. + a = new Roo.htmleditor.FilterKeepChildren({tag : false}); + a.replaceTag(sn); + } + + + var range = editorcore.createRange(); + + range.setStart(stn,0); + range.setEnd(stn,0); + var selection = editorcore.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + + + //_this.updateToolbar(null, null, pn); + _this.updateToolbar(null, null, null); + _this.updateFooter(false); - } else if (sn && sn.tagName != 'BODY') { - // remove and keep parents. - a = new Roo.htmleditor.FilterKeepChildren({tag : false}); - a.removeTag(sn); } - - - var range = editorcore.createRange(); - - range.setStart(stn,0); - range.setEnd(stn,0); - var selection = editorcore.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); - - - //_this.updateToolbar(null, null, pn); - _this.updateToolbar(null, null, null); - _this.updateFooter(false); - } - } - + + - - }); - + }); + } tb.el.on('click', function(e){ e.preventDefault(); // what does this do? @@ -49888,17 +51966,7 @@ Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, { var ans = this.footerEls; var sel = ans[n]; - // pick - var range = this.editorcore.createRange(); - - range.selectNodeContents(sel); - //range.selectNode(sel); - - - var selection = this.editorcore.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); - + this.editorcore.selectNode(sel); this.updateToolbar(null, null, sel); @@ -50467,11 +52535,12 @@ clientValidation Boolean Applies to submit only. Pass true to call fo * @param {Boolean} asString * @return {Object} */ - getValues : function(asString){ + getValues : function(asString) + { if (this.childForms) { // copy values from the child forms Roo.each(this.childForms, function (f) { - this.setValues(f.getValues()); + this.setValues(f.getFieldValues()); // get the full set of data, as we might be copying comboboxes from external into this one. }, this); } @@ -50504,21 +52573,31 @@ clientValidation Boolean Applies to submit only. Pass true to call fo /** * Returns the fields in this form as an object with key/value pairs. * This differs from getValues as it calls getValue on each child item, rather than using dom data. + * Normally this will not return readOnly data + * @param {Boolean} with_readonly return readonly field data. * @return {Object} */ - getFieldValues : function(with_hidden) + getFieldValues : function(with_readonly) { if (this.childForms) { // copy values from the child forms // should this call getFieldValues - probably not as we do not currently copy // hidden fields when we generate.. Roo.each(this.childForms, function (f) { - this.setValues(f.getValues()); + this.setValues(f.getFieldValues()); }, this); } var ret = {}; this.items.each(function(f){ + + if (f.readOnly && with_readonly !== true) { + return; // skip read only values. - this is in theory to stop 'old' values being copied over new ones + // if a subform contains a copy of them. + // if you have subforms with the same editable data, you will need to copy the data back + // and forth. + } + if (!f.getName()) { return; } @@ -51608,7 +53687,7 @@ Roo.form.Action.ACTION_TYPES = { /** * @class Roo.form.Layout * @extends Roo.Component - * @children Roo.form.Column Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem + * @children Roo.form.Column Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem Roo.form.FieldSet * Creates a container for layout and rendering of fields in an {@link Roo.form.Form}. * @constructor * @param {Object} config Configuration options @@ -51759,7 +53838,7 @@ Roo.extend(Roo.form.Layout, Roo.Component, { /** * @class Roo.form.Column * @extends Roo.form.Layout - * @children Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem + * @children Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem Roo.form.FieldSet * Creates a column container for layout and rendering of fields in an {@link Roo.form.Form}. * @constructor * @param {Object} config Configuration options @@ -51794,7 +53873,7 @@ Roo.extend(Roo.form.Column, Roo.form.Layout, { /** * @class Roo.form.Row * @extends Roo.form.Layout - * @children Roo.form.Column Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem + * @children Roo.form.Column Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem Roo.form.FieldSet * Creates a row container for layout and rendering of fields in an {@link Roo.form.Form}. * @constructor * @param {Object} config Configuration options @@ -58056,8 +60135,10 @@ Roo.extend(Roo.grid.Grid, Roo.util.Observable, { * @cfg {Roo.dd.DropTarget} dropTarget An {@link Roo.dd.DropTarget} config */ dropTarget: false, - - + /** + * @cfg {boolean} sortColMenu Sort the column order menu when it shows (usefull for long lists..) default false + */ + sortColMenu : false, // private rendered : false, @@ -60119,13 +62200,28 @@ Roo.extend(Roo.grid.GridView, Roo.grid.AbstractGridView, { beforeColMenuShow : function(){ var cm = this.cm, colCount = cm.getColumnCount(); this.colMenu.removeAll(); + + var items = []; for(var i = 0; i < colCount; i++){ - this.colMenu.add(new Roo.menu.CheckItem({ + items.push({ id: "col-"+cm.getColumnId(i), text: cm.getColumnHeader(i), checked: !cm.isHidden(i), hideOnClick:false - })); + }); + } + + if (this.grid.sortColMenu) { + items.sort(function(a,b) { + if (a.text == b.text) { + return 0; + } + return a.text.toUpperCase() > b.text.toUpperCase() ? 1 : -1; + }); + } + + for(var i = 0; i < colCount; i++){ + this.colMenu.add(new Roo.menu.CheckItem(items[i])); } },