X-Git-Url: http://git.roojs.org/?a=blobdiff_plain;f=roojs-debug.js;h=27f607683cfc9137cb51c7d1ab1977af84eeedc3;hb=b999dac6d220ed742af894af4750d19d0e6dc586;hp=613def5d6ce2ca68cfe0a5df2d51d248f82a2065;hpb=b11759adb5c310754e3e05664534512256246254;p=roojs1 diff --git a/roojs-debug.js b/roojs-debug.js index 613def5d6c..27f607683c 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;
-            
-            
         }
-        //add the br, or p, or something else
-        newEle = doc.createElement('br');
-        docFragment.appendChild(newEle);
     
-        //make the br replace selection
-        
-        range.deleteContents();
+        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;
         
-        range.insertNode(docFragment);
-    
-        //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);
     
-        return false;
+        
+        
          
     }
 };
@@ -46021,24 +46551,51 @@ 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(Roo.htmleditor.Block.cache[id]) != 'undefined') {
-        Roo.htmleditor.Block.cache[id].readElement();
+    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 cls = Roo.htmleditor['Block' + Roo.get(node).attr('data-block')];
+    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("OOps missing block : " + 'Block' + Roo.get(node).attr('data-block'));
+        //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 = {};
@@ -46048,7 +46605,10 @@ Roo.htmleditor.Block.prototype = {
     node : false,
     
      // used by context menu
-    friendly_name : 'Image with caption',
+    friendly_name : 'Based Block',
+    
+    // text for button to delete this element
+    deleteTitle : false,
     
     context : false,
     /**
@@ -46083,14 +46643,17 @@ Roo.htmleditor.Block.prototype = {
             // but kiss for now.
             n = node.getElementsByTagName(tag).item(0);
         }
+        if (!n) {
+            return '';
+        }
         if (attr == 'html') {
             return n.innerHTML;
         }
         if (attr == 'style') {
-            return Roo.get(n).getStyle(style);
+            return n.style[style]; 
         }
         
-        return Roo.get(n).attr(attr);
+        return n.hasAttribute(attr) ? n.getAttribute(attr) : '';
             
     },
     /**
@@ -46121,8 +46684,8 @@ Roo.htmleditor.Block.prototype = {
  * 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} 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 %?
  * 
@@ -46144,45 +46707,167 @@ Roo.extend(Roo.htmleditor.BlockFigure, Roo.htmleditor.Block, {
     
     // setable values.
     image_src: '',
-    
-    align: 'left',
+    align: 'center',
     caption : '',
-    text_align: 'left',
+    caption_display : 'block',
+    width : '100%',
+    cls : '',
+    href: '',
+    video_url : '',
     
-    width : '46%',
-    margin: '2%',
+    // 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",
     
-    context : { // ?? static really
-        width : {
-            title: "Width",
-            width: 40
-            // ?? number
-        },
-        margin : {
-            title: "Margin",
-            width: 40
-            // ?? number
-        },
-        align: {
-            title: "Align",
-            opts : [[ "left"],[ "right"]],
-            width : 80
+    contextMenu : function(toolbar)
+    {
+        
+        var block = function() {
+            return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode);
+        };
+        
+        
+        var rooui =  typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap;
+        
+        var syncValue = toolbar.editorcore.syncValue;
+        
+        var fields = {};
+        
+        return [
+             {
+                xtype : 'TextItem',
+                text : "Source: ",
+                xns : rooui.Toolbar  //Boostrap?
+            },
+            {
+                xtype : '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
+                }
+            },
             
-        },
-        text_align: {
-            title: "Caption Align",
-            opts : [ [ "left"],[ "right"],[ "center"]],
-            width : 80
-        },
+            
+            {
+                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
+            }
+        ];
         
-       
-        image_src : {
-            title: "Src",
-            width: 220
-        }
     },
     /**
      * create a DomHelper friendly object - for use with
@@ -46193,48 +46878,115 @@ Roo.extend(Roo.htmleditor.BlockFigure, Roo.htmleditor.Block, {
         var d = document.createElement('div');
         d.innerHTML = this.caption;
         
-        return {
+        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 + ] + }; + } + + + 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: 'table', + display: 'block', float : this.align , - width : this.width, - margin: this.margin + 'max-width': this.width, + width : 'auto', + margin: m, + padding: '10px' + }, + + + align : this.align, cn : [ - { - tag : 'img', - src : this.image_src, - alt : d.innerText.replace(/\n/g, " "), // removeHTML.. - style: { - width: '100%' - } - }, + img, + { tag: 'figcaption', contenteditable : true, style : { - 'text-align': this.text_align + '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', 'style', 'float'); + + 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', 'width'); - this.margin = this.getVal(node, 'figure', 'style', 'margin'); + //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'); - } + }, + removeNode : function() + { + return this.node; + } @@ -46243,6 +46995,1244 @@ Roo.extend(Roo.htmleditor.BlockFigure, Roo.htmleditor.Block, { +}) + + + +/** + * @class Roo.htmleditor.BlockTable + * Block that manages a table + * + * @constructor + * Create a new Filter. + * @param {Object} config Configuration options + */ + +Roo.htmleditor.BlockTable = function(cfg) +{ + if (cfg.node) { + this.readElement(cfg.node); + this.updateElement(cfg.node); + } + Roo.apply(this, cfg); + if (!cfg.node) { + this.rows = []; + for(var r = 0; r < this.no_row; r++) { + this.rows[r] = []; + for(var c = 0; c < this.no_col; c++) { + this.rows[r][c] = this.emptyCell(); + } + } + } + + +} +Roo.extend(Roo.htmleditor.BlockTable, Roo.htmleditor.Block, { + + rows : false, + no_col : 1, + no_row : 1, + + + width: '100%', + + // used by context menu + friendly_name : 'Table', + deleteTitle : 'Delete Table', + // context menu is drawn once.. + + contextMenu : function(toolbar) + { + + var block = function() { + return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode); + }; + + + var rooui = typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap; + + var syncValue = toolbar.editorcore.syncValue; + + var fields = {}; + + return [ + { + xtype : 'TextItem', + text : "Width: ", + xns : rooui.Toolbar //Boostrap? + }, + { + xtype : 'ComboBox', + allowBlank : false, + displayField : 'val', + editable : true, + listWidth : 100, + triggerAction : 'all', + typeAhead : true, + valueField : 'val', + width : 100, + name : 'width', + listeners : { + select : function (combo, r, index) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + var b = block(); + b.width = r.get('val'); + b.updateElement(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.form, + store : { + xtype : 'SimpleStore', + data : [ + ['100%'], + ['auto'] + ], + fields : [ 'val'], + xns : Roo.data + } + }, + // -------- Cols + + { + xtype : 'TextItem', + text : "Columns: ", + xns : rooui.Toolbar //Boostrap? + }, + + { + xtype : 'Button', + text: '-', + listeners : { + click : function (_self, e) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + block().removeColumn(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + }, + { + xtype : 'Button', + text: '+', + listeners : { + click : function (_self, e) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + block().addColumn(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + }, + // -------- ROWS + { + xtype : 'TextItem', + text : "Rows: ", + xns : rooui.Toolbar //Boostrap? + }, + + { + xtype : 'Button', + text: '-', + listeners : { + click : function (_self, e) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + block().removeRow(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + }, + { + xtype : 'Button', + text: '+', + listeners : { + click : function (_self, e) + { + block().addRow(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + }, + // -------- ROWS + { + xtype : 'Button', + text: 'Reset Column Widths', + listeners : { + + click : function (_self, e) + { + block().resetWidths(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + } + + + + ]; + + }, + + + /** + * create a DomHelper friendly object - for use with + * Roo.DomHelper.markup / overwrite / etc.. + * ?? should it be called with option to hide all editing features? + */ + toObject : function() + { + + var ret = { + tag : 'table', + contenteditable : 'false', // this stops cell selection from picking the table. + 'data-block' : 'Table', + style : { + width: this.width, + border : 'solid 1px #000', // ??? hard coded? + 'border-collapse' : 'collapse' + }, + cn : [ + { tag : 'tbody' , cn : [] } + ] + }; + + // do we have a head = not really + var ncols = 0; + Roo.each(this.rows, function( row ) { + var tr = { + tag: 'tr', + style : { + margin: '6px', + border : 'solid 1px #000', + textAlign : 'left' + }, + cn : [ ] + }; + + ret.cn[0].cn.push(tr); + // does the row have any properties? ?? height? + var nc = 0; + Roo.each(row, function( cell ) { + + var td = { + tag : 'td', + contenteditable : 'true', + 'data-block' : 'Td', + html : cell.html, + style : cell.style + }; + if (cell.colspan > 1) { + td.colspan = cell.colspan ; + nc += cell.colspan; + } else { + nc++; + } + if (cell.rowspan > 1) { + td.rowspan = cell.rowspan ; + } + + + // widths ? + tr.cn.push(td); + + + }, this); + ncols = Math.max(nc, ncols); + + + }, this); + // add the header row.. + + ncols++; + + + return ret; + + }, + + readElement : function(node) + { + node = node ? node : this.node ; + this.width = this.getVal(node, true, 'style', 'width') || '100%'; + + this.rows = []; + this.no_row = 0; + var trs = Array.from(node.rows); + trs.forEach(function(tr) { + var row = []; + this.rows.push(row); + + this.no_row++; + var no_column = 0; + Array.from(tr.cells).forEach(function(td) { + + var add = { + colspan : td.hasAttribute('colspan') ? td.getAttribute('colspan')*1 : 1, + rowspan : td.hasAttribute('rowspan') ? td.getAttribute('rowspan')*1 : 1, + style : td.hasAttribute('style') ? td.getAttribute('style') : '', + html : td.innerHTML + }; + no_column += add.colspan; + + + row.push(add); + + + },this); + this.no_col = Math.max(this.no_col, no_column); + + + },this); + + + }, + normalizeRows: function() + { + var ret= []; + var rid = -1; + this.rows.forEach(function(row) { + rid++; + ret[rid] = []; + row = this.normalizeRow(row); + var cid = 0; + row.forEach(function(c) { + while (typeof(ret[rid][cid]) != 'undefined') { + cid++; + } + if (typeof(ret[rid]) == 'undefined') { + ret[rid] = []; + } + ret[rid][cid] = c; + c.row = rid; + c.col = cid; + if (c.rowspan < 2) { + return; + } + + for(var i = 1 ;i < c.rowspan; i++) { + if (typeof(ret[rid+i]) == 'undefined') { + ret[rid+i] = []; + } + ret[rid+i][cid] = c; + } + }); + }, this); + return ret; + + }, + + normalizeRow: function(row) + { + var ret= []; + row.forEach(function(c) { + if (c.colspan < 2) { + ret.push(c); + return; + } + for(var i =0 ;i < c.colspan; i++) { + ret.push(c); + } + }); + return ret; + + }, + + deleteColumn : function(sel) + { + if (!sel || sel.type != 'col') { + return; + } + if (this.no_col < 2) { + return; + } + + this.rows.forEach(function(row) { + var cols = this.normalizeRow(row); + var col = cols[sel.col]; + if (col.colspan > 1) { + col.colspan --; + } else { + row.remove(col); + } + + }, this); + this.no_col--; + + }, + removeColumn : function() + { + this.deleteColumn({ + type: 'col', + col : this.no_col-1 + }); + this.updateElement(); + }, + + + addColumn : function() + { + + this.rows.forEach(function(row) { + row.push(this.emptyCell()); + + }, this); + this.updateElement(); + }, + + deleteRow : function(sel) + { + if (!sel || sel.type != 'row') { + return; + } + + if (this.no_row < 2) { + return; + } + + var rows = this.normalizeRows(); + + + rows[sel.row].forEach(function(col) { + if (col.rowspan > 1) { + col.rowspan--; + } else { + col.remove = 1; // flage it as removed. + } + + }, this); + var newrows = []; + this.rows.forEach(function(row) { + newrow = []; + row.forEach(function(c) { + if (typeof(c.remove) == 'undefined') { + newrow.push(c); + } + + }); + if (newrow.length > 0) { + newrows.push(row); + } + }); + this.rows = newrows; + + + + this.no_row--; + this.updateElement(); + + }, + removeRow : function() + { + this.deleteRow({ + type: 'row', + row : this.no_row-1 + }); + + }, + + + addRow : function() + { + + var row = []; + for (var i = 0; i < this.no_col; i++ ) { + + row.push(this.emptyCell()); + + } + this.rows.push(row); + this.updateElement(); + + }, + + // the default cell object... at present... + emptyCell : function() { + return (new Roo.htmleditor.BlockTd({})).toObject(); + + + }, + + removeNode : function() + { + return this.node; + }, + + + + resetWidths : function() + { + Array.from(this.node.getElementsByTagName('td')).forEach(function(n) { + var nn = Roo.htmleditor.Block.factory(n); + nn.width = ''; + nn.updateElement(n); + }); + } + + + + +}) + +/** + * + * editing a TD? + * + * since selections really work on the table cell, then editing really should work from there + * + * The original plan was to support merging etc... - but that may not be needed yet.. + * + * So this simple version will support: + * add/remove cols + * adjust the width +/- + * reset the width... + * + * + */ + + + + +/** + * @class Roo.htmleditor.BlockTable + * Block that manages a table + * + * @constructor + * Create a new Filter. + * @param {Object} config Configuration options + */ + +Roo.htmleditor.BlockTd = function(cfg) +{ + if (cfg.node) { + this.readElement(cfg.node); + this.updateElement(cfg.node); + } + Roo.apply(this, cfg); + + + +} +Roo.extend(Roo.htmleditor.BlockTd, Roo.htmleditor.Block, { + + node : false, + + width: '', + textAlign : 'left', + valign : 'top', + + colspan : 1, + rowspan : 1, + + + // used by context menu + friendly_name : 'Table Cell', + deleteTitle : false, // use our customer delete + + // context menu is drawn once.. + + contextMenu : function(toolbar) + { + + var cell = function() { + return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode); + }; + + var table = function() { + return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode.closest('table')); + }; + + var lr = false; + var saveSel = function() + { + lr = toolbar.editorcore.getSelection().getRangeAt(0); + } + var restoreSel = function() + { + if (lr) { + (function() { + toolbar.editorcore.focus(); + var cr = toolbar.editorcore.getSelection(); + cr.removeAllRanges(); + cr.addRange(lr); + toolbar.editorcore.onEditorEvent(); + }).defer(10, this); + + + } + } + + var rooui = typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap; + + var syncValue = toolbar.editorcore.syncValue; + + var fields = {}; + + return [ + { + xtype : 'Button', + text : 'Edit Table', + listeners : { + click : function() { + var t = toolbar.tb.selectedNode.closest('table'); + toolbar.editorcore.selectNode(t); + toolbar.editorcore.onEditorEvent(); + } + } + + }, + + + + { + xtype : 'TextItem', + text : "Column Width: ", + xns : rooui.Toolbar + + }, + { + xtype : 'Button', + text: '-', + listeners : { + click : function (_self, e) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + cell().shrinkColumn(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + }, + { + xtype : 'Button', + text: '+', + listeners : { + click : function (_self, e) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + cell().growColumn(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + }, + + { + xtype : 'TextItem', + text : "Vertical Align: ", + xns : rooui.Toolbar //Boostrap? + }, + { + xtype : 'ComboBox', + allowBlank : false, + displayField : 'val', + editable : true, + listWidth : 100, + triggerAction : 'all', + typeAhead : true, + valueField : 'val', + width : 100, + name : 'valign', + listeners : { + select : function (combo, r, index) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + var b = cell(); + b.valign = r.get('val'); + b.updateElement(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.form, + store : { + xtype : 'SimpleStore', + data : [ + ['top'], + ['middle'], + ['bottom'] // there are afew more... + ], + fields : [ 'val'], + xns : Roo.data + } + }, + + { + xtype : 'TextItem', + text : "Merge Cells: ", + xns : rooui.Toolbar + + }, + + + { + xtype : 'Button', + text: 'Right', + listeners : { + click : function (_self, e) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + cell().mergeRight(); + //block().growColumn(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + }, + + { + xtype : 'Button', + text: 'Below', + listeners : { + click : function (_self, e) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + cell().mergeBelow(); + //block().growColumn(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + }, + { + xtype : 'TextItem', + text : "| ", + xns : rooui.Toolbar + + }, + + { + xtype : 'Button', + text: 'Split', + listeners : { + click : function (_self, e) + { + //toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + cell().split(); + syncValue(); + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + toolbar.editorcore.onEditorEvent(); + + } + }, + xns : rooui.Toolbar + }, + { + xtype : 'Fill', + xns : rooui.Toolbar + + }, + + + { + xtype : 'Button', + text: 'Delete', + + xns : rooui.Toolbar, + menu : { + xtype : 'Menu', + xns : rooui.menu, + items : [ + { + xtype : 'Item', + html: 'Column', + listeners : { + click : function (_self, e) + { + var t = table(); + + cell().deleteColumn(); + syncValue(); + toolbar.editorcore.selectNode(t.node); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.menu + }, + { + xtype : 'Item', + html: 'Row', + listeners : { + click : function (_self, e) + { + var t = table(); + cell().deleteRow(); + syncValue(); + + toolbar.editorcore.selectNode(t.node); + toolbar.editorcore.onEditorEvent(); + + } + }, + xns : rooui.menu + }, + { + xtype : 'Separator', + xns : rooui.menu + }, + { + xtype : 'Item', + html: 'Table', + listeners : { + click : function (_self, e) + { + var t = table(); + var nn = t.node.nextSibling || t.node.previousSibling; + t.node.parentNode.removeChild(t.node); + if (nn) { + toolbar.editorcore.selectNode(nn, true); + } + toolbar.editorcore.onEditorEvent(); + + } + }, + xns : rooui.menu + } + ] + } + } + + // align... << fixme + + ]; + + }, + + + /** + * create a DomHelper friendly object - for use with + * Roo.DomHelper.markup / overwrite / etc.. + * ?? should it be called with option to hide all editing features? + */ + /** + * create a DomHelper friendly object - for use with + * Roo.DomHelper.markup / overwrite / etc.. + * ?? should it be called with option to hide all editing features? + */ + toObject : function() + { + + var ret = { + tag : 'td', + contenteditable : 'true', // this stops cell selection from picking the table. + 'data-block' : 'Td', + valign : this.valign, + style : { + 'text-align' : this.textAlign, + border : 'solid 1px rgb(0, 0, 0)', // ??? hard coded? + 'border-collapse' : 'collapse', + padding : '6px', // 8 for desktop / 4 for mobile + 'vertical-align': this.valign + }, + html : this.html + }; + if (this.width != '') { + ret.width = this.width; + ret.style.width = this.width; + } + + + if (this.colspan > 1) { + ret.colspan = this.colspan ; + } + if (this.rowspan > 1) { + ret.rowspan = this.rowspan ; + } + + + + return ret; + + }, + + readElement : function(node) + { + node = node ? node : this.node ; + this.width = node.style.width; + this.colspan = Math.max(1,1*node.getAttribute('colspan')); + this.rowspan = Math.max(1,1*node.getAttribute('rowspan')); + this.html = node.innerHTML; + + + }, + + // 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. + } + + + return ret; + + }, + + + + + mergeRight: function() + { + + // get the contents of the next cell along.. + var tr = this.node.closest('tr'); + var i = Array.prototype.indexOf.call(tr.childNodes, this.node); + if (i >= tr.childNodes.length - 1) { + return; // no cells on right to merge with. + } + var table = this.toTableArray(); + + if (typeof(table[this.cellData.row][this.cellData.col+this.cellData.colspan]) == 'undefined') { + return; // nothing right? + } + var rc = table[this.cellData.row][this.cellData.col+this.cellData.colspan]; + // right cell - must be same rowspan and on the same row. + if (rc.rowspan != this.cellData.rowspan || rc.row != this.cellData.row) { + return; // right hand side is not same rowspan. + } + + + + this.node.innerHTML += ' ' + rc.cell.innerHTML; + tr.removeChild(rc.cell); + this.colspan += rc.colspan; + this.node.setAttribute('colspan', this.colspan); + + }, + + + mergeBelow : function() + { + var table = this.toTableArray(); + if (typeof(table[this.cellData.row+this.cellData.rowspan]) == 'undefined') { + return; // no row below + } + if (typeof(table[this.cellData.row+this.cellData.rowspan][this.cellData.col]) == 'undefined') { + return; // nothing right? + } + var rc = table[this.cellData.row+this.cellData.rowspan][this.cellData.col]; + + if (rc.colspan != this.cellData.colspan || rc.col != this.cellData.col) { + return; // right hand side is not same rowspan. + } + this.node.innerHTML = this.node.innerHTML + rc.cell.innerHTML ; + rc.cell.parentNode.removeChild(rc.cell); + this.rowspan += rc.rowspan; + this.node.setAttribute('rowspan', this.rowspan); + }, + + split: function() + { + if (this.node.rowSpan < 2 && this.node.colSpan < 2) { + return; + } + var table = this.toTableArray(); + var cd = this.cellData; + this.rowspan = 1; + this.colspan = 1; + + for(var r = cd.row; r < cd.row + cd.rowspan; r++) { + + + + for(var c = cd.col; c < cd.col + cd.colspan; c++) { + if (r == cd.row && c == cd.col) { + this.node.removeAttribute('rowspan'); + this.node.removeAttribute('colspan'); + 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); + + + + }, + + + + redrawAllCells: function(table) + { + + + var tab = this.node.closest('tr').closest('table'); + var ctr = tab.rows[0].parentNode; + Array.from(tab.rows).forEach(function(r, ri){ + + Array.from(r.cells).forEach(function(ce, ci){ + ce.parentNode.removeChild(ce); + }); + r.parentNode.removeChild(r); + }); + for(var r = 0 ; r < table.length; r++) { + var re = tab.rows[r]; + + var re = tab.ownerDocument.createElement('tr'); + ctr.appendChild(re); + for(var c = 0 ; c < table[r].length; c++) { + if (table[r][c].cell === false) { + continue; + } + + re.appendChild(table[r][c].cell); + + table[r][c].cell = false; + } + } + + }, + updateWidths : function(table) + { + for(var r = 0 ; r < table.length; r++) { + + for(var c = 0 ; c < table[r].length; c++) { + if (table[r][c].cell === false) { + continue; + } + + if (this.colWidths[0] != false && table[r][c].colspan < 2) { + var el = Roo.htmleditor.Block.factory(table[r][c].cell); + el.width = Math.floor(this.colWidths[c]) +'%'; + el.updateElement(el.node); + } + table[r][c].cell = false; // done + } + } + }, + normalizeWidths : function(table) + { + + if (this.colWidths[0] === false) { + var nw = 100.0 / this.colWidths.length; + this.colWidths.forEach(function(w,i) { + this.colWidths[i] = nw; + },this); + return; + } + + var t = 0, missing = []; + + this.colWidths.forEach(function(w,i) { + //if you mix % and + this.colWidths[i] = this.colWidths[i] == '' ? 0 : (this.colWidths[i]+'').replace(/[^0-9]+/g,'')*1; + var add = this.colWidths[i]; + if (add > 0) { + t+=add; + return; + } + missing.push(i); + + + },this); + var nc = this.colWidths.length; + if (missing.length) { + var mult = (nc - missing.length) / (1.0 * nc); + var t = mult * t; + var ew = (100 -t) / (1.0 * missing.length); + this.colWidths.forEach(function(w,i) { + if (w > 0) { + this.colWidths[i] = w * mult; + return; + } + + this.colWidths[i] = ew; + }, this); + // have to make up numbers.. + + } + // now we should have all the widths.. + + + }, + + shrinkColumn : function() + { + var table = this.toTableArray(); + this.normalizeWidths(table); + var col = this.cellData.col; + var nw = this.colWidths[col] * 0.8; + if (nw < 5) { + return; + } + var otherAdd = (this.colWidths[col] * 0.2) / (this.colWidths.length -1); + this.colWidths.forEach(function(w,i) { + if (i == col) { + this.colWidths[i] = nw; + return; + } + this.colWidths[i] += otherAdd + }, this); + this.updateWidths(table); + + }, + growColumn : function() + { + var table = this.toTableArray(); + this.normalizeWidths(table); + var col = this.cellData.col; + var nw = this.colWidths[col] * 1.2; + if (nw > 90) { + return; + } + var otherSub = (this.colWidths[col] * 0.2) / (this.colWidths.length -1); + this.colWidths.forEach(function(w,i) { + if (i == col) { + this.colWidths[i] = nw; + return; + } + this.colWidths[i] -= otherSub + }, this); + this.updateWidths(table); + + }, + deleteRow : function() + { + // delete this rows 'tr' + // if any of the cells in this row have a rowspan > 1 && row!= this row.. + // then reduce the rowspan. + var table = this.toTableArray(); + // this.cellData.row; + for (var i =0;i< table[this.cellData.row].length ; i++) { + var c = table[this.cellData.row][i]; + if (c.row != this.cellData.row) { + + c.rowspan--; + c.cell.setAttribute('rowspan', c.rowspan); + continue; + } + if (c.rowspan > 1) { + c.rowspan--; + c.cell.setAttribute('rowspan', c.rowspan); + } + } + table.splice(this.cellData.row,1); + this.redrawAllCells(table); + + }, + deleteColumn : function() + { + var table = this.toTableArray(); + + for (var i =0;i< table.length ; i++) { + var c = table[i][this.cellData.col]; + if (c.col != this.cellData.col) { + table[i][this.cellData.col].colspan--; + } else if (c.colspan > 1) { + c.colspan--; + c.cell.setAttribute('colspan', c.colspan); + } + table[i].splice(this.cellData.col,1); + } + + this.redrawAllCells(table); + } + + + + }) //