X-Git-Url: http://git.roojs.org/?p=roojs1;a=blobdiff_plain;f=roojs-debug.js;h=f903c90a4242a29c4f6985780eace68dd011b17d;hp=d2b15ccb1ab0888b3f4c086b3e19e2dcdcb63aa6;hb=d6d1e1fe78ba08b1a873583bb12bf05588ff45ae;hpb=18480d449e889bafc18e683dca94b2ff4729dbd4 diff --git a/roojs-debug.js b/roojs-debug.js index d2b15ccb1a..f903c90a42 100644 --- a/roojs-debug.js +++ b/roojs-debug.js @@ -4907,468 +4907,7 @@ 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. @@ -45066,4377 +44605,7 @@ Roo.extend(Roo.form.Radio, Roo.form.Checkbox, { } -});Roo.rtf = {}; // namespace -Roo.rtf.Hex = function(hex) -{ - this.hexstr = hex; -}; -Roo.rtf.Paragraph = function(opts) -{ - this.content = []; ///??? is that used? -};Roo.rtf.Span = function(opts) -{ - this.value = opts.value; -}; - -Roo.rtf.Group = function(parent) -{ - // we dont want to acutally store parent - it will make debug a nightmare.. - this.content = []; - this.cn = []; - - - -}; - -Roo.rtf.Group.prototype = { - ignorable : false, - content: false, - cn: false, - addContent : function(node) { - // could set styles... - this.content.push(node); - }, - addChild : function(cn) - { - this.cn.push(cn); - }, - // only for images really... - toDataURL : function() - { - var mimetype = false; - switch(true) { - case this.content.filter(function(a) { return a.value == 'pngblip' } ).length > 0: - mimetype = "image/png"; - break; - case this.content.filter(function(a) { return a.value == 'jpegblip' } ).length > 0: - mimetype = "image/jpeg"; - break; - default : - return 'about:blank'; // ?? error? - } - - - var hexstring = this.content[this.content.length-1].value; - - return 'data:' + mimetype + ';base64,' + btoa(hexstring.match(/\w{2}/g).map(function(a) { - return String.fromCharCode(parseInt(a, 16)); - }).join("")); - } - -}; -// this looks like it's normally the {rtf{ .... }} -Roo.rtf.Document = function() -{ - // we dont want to acutally store parent - it will make debug a nightmare.. - this.rtlch = []; - this.content = []; - this.cn = []; - -}; -Roo.extend(Roo.rtf.Document, Roo.rtf.Group, { - addChild : function(cn) - { - this.cn.push(cn); - switch(cn.type) { - case 'rtlch': // most content seems to be inside this?? - case 'listtext': - case 'shpinst': - this.rtlch.push(cn); - return; - default: - this[cn.type] = cn; - } - - }, - - getElementsByType : function(type) - { - var ret = []; - this._getElementsByType(type, ret, this.cn, 'rtf'); - return ret; - }, - _getElementsByType : function (type, ret, search_array, path) - { - search_array.forEach(function(n,i) { - if (n.type == type) { - n.path = path + '/' + n.type + ':' + i; - ret.push(n); - } - if (n.cn.length > 0) { - this._getElementsByType(type, ret, n.cn, path + '/' + n.type+':'+i); - } - },this); - } - -}); - -Roo.rtf.Ctrl = function(opts) -{ - this.value = opts.value; - this.param = opts.param; -}; -/** - * - * - * based on this https://github.com/iarna/rtf-parser - * it's really only designed to extract pict from pasted RTF - * - * usage: - * - * var images = new Roo.rtf.Parser().parse(a_string).filter(function(g) { return g.type == 'pict'; }); - * - * - */ - - - - - -Roo.rtf.Parser = function(text) { - //super({objectMode: true}) - this.text = ''; - this.parserState = this.parseText; - - // these are for interpeter... - this.doc = {}; - ///this.parserState = this.parseTop - this.groupStack = []; - this.hexStore = []; - this.doc = false; - - this.groups = []; // where we put the return. - - for (var ii = 0; ii < text.length; ++ii) { - ++this.cpos; - - if (text[ii] === '\n') { - ++this.row; - this.col = 1; - } else { - ++this.col; - } - this.parserState(text[ii]); - } - - - -}; -Roo.rtf.Parser.prototype = { - text : '', // string being parsed.. - controlWord : '', - controlWordParam : '', - hexChar : '', - doc : false, - group: false, - groupStack : false, - hexStore : false, - - - cpos : 0, - row : 1, // reportin? - col : 1, // - - - push : function (el) - { - var m = 'cmd'+ el.type; - if (typeof(this[m]) == 'undefined') { - Roo.log('invalid cmd:' + el.type); - return; - } - this[m](el); - //Roo.log(el); - }, - flushHexStore : function() - { - if (this.hexStore.length < 1) { - return; - } - var hexstr = this.hexStore.map( - function(cmd) { - return cmd.value; - }).join(''); - - this.group.addContent( new Roo.rtf.Hex( hexstr )); - - - this.hexStore.splice(0) - - }, - - cmdgroupstart : function() - { - this.flushHexStore(); - if (this.group) { - this.groupStack.push(this.group); - } - // parent.. - if (this.doc === false) { - this.group = this.doc = new Roo.rtf.Document(); - return; - - } - this.group = new Roo.rtf.Group(this.group); - }, - cmdignorable : function() - { - this.flushHexStore(); - this.group.ignorable = true; - }, - cmdendparagraph : function() - { - this.flushHexStore(); - this.group.addContent(new Roo.rtf.Paragraph()); - }, - cmdgroupend : function () - { - this.flushHexStore(); - var endingGroup = this.group; - - - this.group = this.groupStack.pop(); - if (this.group) { - this.group.addChild(endingGroup); - } - - - - var doc = this.group || this.doc; - //if (endingGroup instanceof FontTable) { - // doc.fonts = endingGroup.table - //} else if (endingGroup instanceof ColorTable) { - // doc.colors = endingGroup.table - //} else if (endingGroup !== this.doc && !endingGroup.get('ignorable')) { - if (endingGroup.ignorable === false) { - //code - this.groups.push(endingGroup); - // Roo.log( endingGroup ); - } - //Roo.each(endingGroup.content, function(item)) { - // doc.addContent(item); - //} - //process.emit('debug', 'GROUP END', endingGroup.type, endingGroup.get('ignorable')) - //} - }, - cmdtext : function (cmd) - { - this.flushHexStore(); - if (!this.group) { // an RTF fragment, missing the {\rtf1 header - //this.group = this.doc - } - this.group.addContent(new Roo.rtf.Span(cmd)); - }, - cmdcontrolword : function (cmd) - { - this.flushHexStore(); - if (!this.group.type) { - this.group.type = cmd.value; - return; - } - this.group.addContent(new Roo.rtf.Ctrl(cmd)); - // we actually don't care about ctrl words... - return ; - /* - var method = 'ctrl$' + cmd.value.replace(/-(.)/g, (_, char) => char.toUpperCase()) - if (this[method]) { - this[method](cmd.param) - } else { - if (!this.group.get('ignorable')) process.emit('debug', method, cmd.param) - } - */ - }, - cmdhexchar : function(cmd) { - this.hexStore.push(cmd); - }, - cmderror : function(cmd) { - throw new Exception (cmd.value); - }, - - /* - _flush (done) { - if (this.text !== '\u0000') this.emitText() - done() - } - */ - - - parseText : function(c) - { - if (c === '\\') { - this.parserState = this.parseEscapes; - } else if (c === '{') { - this.emitStartGroup(); - } else if (c === '}') { - this.emitEndGroup(); - } else if (c === '\x0A' || c === '\x0D') { - // cr/lf are noise chars - } else { - this.text += c; - } - }, - - parseEscapes: function (c) - { - if (c === '\\' || c === '{' || c === '}') { - this.text += c; - this.parserState = this.parseText; - } else { - this.parserState = this.parseControlSymbol; - this.parseControlSymbol(c); - } - }, - parseControlSymbol: function(c) - { - if (c === '~') { - this.text += '\u00a0'; // nbsp - this.parserState = this.parseText - } else if (c === '-') { - this.text += '\u00ad'; // soft hyphen - } else if (c === '_') { - this.text += '\u2011'; // non-breaking hyphen - } else if (c === '*') { - this.emitIgnorable(); - this.parserState = this.parseText; - } else if (c === "'") { - this.parserState = this.parseHexChar; - } else if (c === '|') { // formula cacter - this.emitFormula(); - this.parserState = this.parseText; - } else if (c === ':') { // subentry in an index entry - this.emitIndexSubEntry(); - this.parserState = this.parseText; - } else if (c === '\x0a') { - this.emitEndParagraph(); - this.parserState = this.parseText; - } else if (c === '\x0d') { - this.emitEndParagraph(); - this.parserState = this.parseText; - } else { - this.parserState = this.parseControlWord; - this.parseControlWord(c); - } - }, - parseHexChar: function (c) - { - if (/^[A-Fa-f0-9]$/.test(c)) { - this.hexChar += c; - if (this.hexChar.length >= 2) { - this.emitHexChar(); - this.parserState = this.parseText; - } - return; - } - this.emitError("Invalid character \"" + c + "\" in hex literal."); - this.parserState = this.parseText; - - }, - parseControlWord : function(c) - { - if (c === ' ') { - this.emitControlWord(); - this.parserState = this.parseText; - } else if (/^[-\d]$/.test(c)) { - this.parserState = this.parseControlWordParam; - this.controlWordParam += c; - } else if (/^[A-Za-z]$/.test(c)) { - this.controlWord += c; - } else { - this.emitControlWord(); - this.parserState = this.parseText; - this.parseText(c); - } - }, - parseControlWordParam : function (c) { - if (/^\d$/.test(c)) { - this.controlWordParam += c; - } else if (c === ' ') { - this.emitControlWord(); - this.parserState = this.parseText; - } else { - this.emitControlWord(); - this.parserState = this.parseText; - this.parseText(c); - } - }, - - - - - emitText : function () { - if (this.text === '') { - return; - } - this.push({ - type: 'text', - value: this.text, - pos: this.cpos, - row: this.row, - col: this.col - }); - this.text = '' - }, - emitControlWord : function () - { - this.emitText(); - if (this.controlWord === '') { - this.emitError('empty control word'); - } else { - this.push({ - type: 'controlword', - value: this.controlWord, - param: this.controlWordParam !== '' && Number(this.controlWordParam), - pos: this.cpos, - row: this.row, - col: this.col - }); - } - this.controlWord = ''; - this.controlWordParam = ''; - }, - emitStartGroup : function () - { - this.emitText(); - this.push({ - type: 'groupstart', - pos: this.cpos, - row: this.row, - col: this.col - }); - }, - emitEndGroup : function () - { - this.emitText(); - this.push({ - type: 'groupend', - pos: this.cpos, - row: this.row, - col: this.col - }); - }, - emitIgnorable : function () - { - this.emitText(); - this.push({ - type: 'ignorable', - pos: this.cpos, - row: this.row, - col: this.col - }); - }, - emitHexChar : function () - { - this.emitText(); - this.push({ - type: 'hexchar', - value: this.hexChar, - pos: this.cpos, - row: this.row, - col: this.col - }); - this.hexChar = '' - }, - emitError : function (message) - { - this.emitText(); - this.push({ - type: 'error', - value: message, - row: this.row, - col: this.col, - char: this.cpos //, - //stack: new Error().stack - }); - }, - emitEndParagraph : function () { - this.emitText(); - this.push({ - type: 'endparagraph', - pos: this.cpos, - row: this.row, - col: this.col - }); - } - -} ; -Roo.htmleditor = {}; - -/** - * @class Roo.htmleditor.Filter - * Base Class for filtering htmleditor stuff. - do not use this directly - extend it. - * @cfg {DomElement} node The node to iterate and filter - * @cfg {boolean|String|Array} tag Tags to replace - * @constructor - * Create a new Filter. - * @param {Object} config Configuration options - */ - - - -Roo.htmleditor.Filter = function(cfg) { - Roo.apply(this.cfg); - // this does not actually call walk as it's really just a abstract class -} - - -Roo.htmleditor.Filter.prototype = { - - node: false, - - tag: false, - - // overrride to do replace comments. - replaceComment : false, - - // overrride to do replace or do stuff with tags.. - replaceTag : false, - - walk : function(dom) - { - Roo.each( Array.from(dom.childNodes), function( e ) { - switch(true) { - - case e.nodeType == 8 && this.replaceComment !== false: // comment - this.replaceComment(e); - return; - - case e.nodeType != 1: //not a node. - return; - - case this.tag === true: // everything - case typeof(this.tag) == 'object' && this.tag.indexOf(e.tagName) > -1: // array and it matches. - case typeof(this.tag) == 'string' && this.tag == e.tagName: // array and it matches. - if (this.replaceTag && false === this.replaceTag(e)) { - return; - } - if (e.hasChildNodes()) { - this.walk(e); - } - return; - - default: // tags .. that do not match. - if (e.hasChildNodes()) { - this.walk(e); - } - } - - }, this); - - } -}; - -/** - * @class Roo.htmleditor.FilterAttributes - * clean attributes and styles including http:// etc.. in attribute - * @constructor -* Run a new Attribute Filter -* @param {Object} config Configuration options - */ -Roo.htmleditor.FilterAttributes = function(cfg) -{ - Roo.apply(this, cfg); - this.attrib_black = this.attrib_black || []; - this.attrib_white = this.attrib_white || []; - - this.attrib_clean = this.attrib_clean || []; - this.style_white = this.style_white || []; - this.style_black = this.style_black || []; - this.walk(cfg.node); -} - -Roo.extend(Roo.htmleditor.FilterAttributes, Roo.htmleditor.Filter, -{ - tag: true, // all tags - - attrib_black : false, // array - attrib_clean : false, - attrib_white : false, - - style_white : false, - style_black : false, - - - replaceTag : function(node) - { - if (!node.attributes || !node.attributes.length) { - return true; - } - - for (var i = node.attributes.length-1; i > -1 ; i--) { - var a = node.attributes[i]; - //console.log(a); - if (this.attrib_white.length && this.attrib_white.indexOf(a.name.toLowerCase()) < 0) { - node.removeAttribute(a.name); - continue; - } - - - - if (a.name.toLowerCase().substr(0,2)=='on') { - node.removeAttribute(a.name); - continue; - } - - - if (this.attrib_black.indexOf(a.name.toLowerCase()) > -1) { - node.removeAttribute(a.name); - continue; - } - if (this.attrib_clean.indexOf(a.name.toLowerCase()) > -1) { - this.cleanAttr(node,a.name,a.value); // fixme.. - continue; - } - if (a.name == 'style') { - this.cleanStyle(node,a.name,a.value); - continue; - } - /// clean up MS crap.. - // tecnically this should be a list of valid class'es.. - - - if (a.name == 'class') { - if (a.value.match(/^Mso/)) { - node.removeAttribute('class'); - } - - if (a.value.match(/^body$/)) { - node.removeAttribute('class'); - } - continue; - } - - - // style cleanup!? - // class cleanup? - - } - return true; // clean children - }, - - cleanAttr: function(node, n,v) - { - - if (v.match(/^\./) || v.match(/^\//)) { - return; - } - if (v.match(/^(http|https):\/\//) - || v.match(/^mailto:/) - || v.match(/^ftp:/) - || v.match(/^data:/) - ) { - return; - } - if (v.match(/^#/)) { - return; - } - if (v.match(/^\{/)) { // allow template editing. - return; - } -// Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v); - node.removeAttribute(n); - - }, - cleanStyle : function(node, n,v) - { - if (v.match(/expression/)) { //XSS?? should we even bother.. - node.removeAttribute(n); - return; - } - - var parts = v.split(/;/); - var clean = []; - - Roo.each(parts, function(p) { - p = p.replace(/^\s+/g,'').replace(/\s+$/g,''); - if (!p.length) { - return true; - } - var l = p.split(':').shift().replace(/\s+/g,''); - l = l.replace(/^\s+/g,'').replace(/\s+$/g,''); - - if ( this.style_black.length && (this.style_black.indexOf(l) > -1 || this.style_black.indexOf(l.toLowerCase()) > -1)) { - return true; - } - //Roo.log() - // only allow 'c whitelisted system attributes' - if ( this.style_white.length && style_white.indexOf(l) < 0 && style_white.indexOf(l.toLowerCase()) < 0 ) { - return true; - } - - - clean.push(p); - return true; - },this); - if (clean.length) { - node.setAttribute(n, clean.join(';')); - } else { - node.removeAttribute(n); - } - - } - - - - -});/** - * @class Roo.htmleditor.FilterBlack - * remove blacklisted elements. - * @constructor - * Run a new Blacklisted Filter - * @param {Object} config Configuration options - */ - -Roo.htmleditor.FilterBlack = function(cfg) -{ - Roo.apply(this, cfg); - this.walk(cfg.node); -} - -Roo.extend(Roo.htmleditor.FilterBlack, Roo.htmleditor.Filter, -{ - tag : true, // all elements. - - replaceTag : function(n) - { - n.parentNode.removeChild(n); - } -}); -/** - * @class Roo.htmleditor.FilterComment - * remove comments. - * @constructor -* Run a new Comments Filter -* @param {Object} config Configuration options - */ -Roo.htmleditor.FilterComment = function(cfg) -{ - this.walk(cfg.node); -} - -Roo.extend(Roo.htmleditor.FilterComment, Roo.htmleditor.Filter, -{ - - replaceComment : function(n) - { - n.parentNode.removeChild(n); - } -});/** - * @class Roo.htmleditor.FilterKeepChildren - * remove tags but keep children - * @constructor - * Run a new Keep Children Filter - * @param {Object} config Configuration options - */ - -Roo.htmleditor.FilterKeepChildren = function(cfg) -{ - Roo.apply(this, cfg); - if (this.tag === false) { - return; // dont walk.. (you can use this to use this just to do a child removal on a single tag ) - } - this.walk(cfg.node); -} - -Roo.extend(Roo.htmleditor.FilterKeepChildren, Roo.htmleditor.FilterBlack, -{ - - - replaceTag : function(node) - { - // walk children... - //Roo.log(node); - var ar = Array.from(node.childNodes); - //remove first.. - for (var i = 0; i < ar.length; i++) { - if (ar[i].nodeType == 1) { - if ( - (typeof(this.tag) == 'object' && this.tag.indexOf(ar[i].tagName) > -1) - || // array and it matches - (typeof(this.tag) == 'string' && this.tag == ar[i].tagName) - ) { - this.replaceTag(ar[i]); // child is blacklisted as well... - continue; - } - } - } - ar = Array.from(node.childNodes); - for (var i = 0; i < ar.length; i++) { - - node.removeChild(ar[i]); - // what if we need to walk these??? - node.parentNode.insertBefore(ar[i], node); - if (this.tag !== false) { - this.walk(ar[i]); - - } - } - node.parentNode.removeChild(node); - return false; // don't walk children - - - } -});/** - * @class Roo.htmleditor.FilterParagraph - * paragraphs cause a nightmare for shared content - this filter is designed to be called ? at various points when editing - * like on 'push' to remove the

tags and replace them with line breaks. - * @constructor - * Run a new Paragraph Filter - * @param {Object} config Configuration options - */ - -Roo.htmleditor.FilterParagraph = function(cfg) -{ - // no need to apply config. - this.walk(cfg.node); -} - -Roo.extend(Roo.htmleditor.FilterParagraph, Roo.htmleditor.Filter, -{ - - - tag : 'P', - - - replaceTag : function(node) - { - - if (node.childNodes.length == 1 && - node.childNodes[0].nodeType == 3 && - node.childNodes[0].textContent.trim().length < 1 - ) { - // remove and replace with '
'; - node.parentNode.replaceChild(node.ownerDocument.createElement('BR'),node); - return false; // no need to walk.. - } - var ar = Array.from(node.childNodes); - for (var i = 0; i < ar.length; i++) { - node.removeChild(ar[i]); - // what if we need to walk these??? - node.parentNode.insertBefore(ar[i], node); - } - // now what about this? - //

 

- - // double BR. - node.parentNode.insertBefore(node.ownerDocument.createElement('BR'), node); - node.parentNode.insertBefore(node.ownerDocument.createElement('BR'), node); - node.parentNode.removeChild(node); - - return false; - - } - -});/** - * @class Roo.htmleditor.FilterSpan - * filter span's with no attributes out.. - * @constructor - * Run a new Span Filter - * @param {Object} config Configuration options - */ - -Roo.htmleditor.FilterSpan = function(cfg) -{ - // no need to apply config. - this.walk(cfg.node); -} - -Roo.extend(Roo.htmleditor.FilterSpan, Roo.htmleditor.FilterKeepChildren, -{ - - tag : 'SPAN', - - - replaceTag : function(node) - { - if (node.attributes && node.attributes.length > 0) { - return true; // walk if there are any. - } - Roo.htmleditor.FilterKeepChildren.prototype.replaceTag.call(this, node); - return false; - - } - -});/** - * @class Roo.htmleditor.FilterTableWidth - try and remove table width data - as that frequently messes up other stuff. - * - * was cleanTableWidths. - * - * Quite often pasting from word etc.. results in tables with column and widths. - * This does not work well on fluid HTML layouts - like emails. - so this code should hunt an destroy them.. - * - * @constructor - * Run a new Table Filter - * @param {Object} config Configuration options - */ - -Roo.htmleditor.FilterTableWidth = function(cfg) -{ - // no need to apply config. - this.tag = ['TABLE', 'TD', 'TR', 'TH', 'THEAD', 'TBODY' ]; - this.walk(cfg.node); -} - -Roo.extend(Roo.htmleditor.FilterTableWidth, Roo.htmleditor.Filter, -{ - - - - replaceTag: function(node) { - - - - if (node.hasAttribute('width')) { - node.removeAttribute('width'); - } - - - if (node.hasAttribute("style")) { - // pretty basic... - - var styles = node.getAttribute("style").split(";"); - var nstyle = []; - Roo.each(styles, function(s) { - if (!s.match(/:/)) { - return; - } - var kv = s.split(":"); - if (kv[0].match(/^\s*(width|min-width)\s*$/)) { - return; - } - // what ever is left... we allow. - nstyle.push(s); - }); - node.setAttribute("style", nstyle.length ? nstyle.join(';') : ''); - if (!nstyle.length) { - node.removeAttribute('style'); - } - } - - return true; // continue doing children.. - } -});/** - * @class Roo.htmleditor.FilterWord - * try and clean up all the mess that Word generates. - * - * This is the 'nice version' - see 'Heavy' that white lists a very short list of elements, and multi-filters - - * @constructor - * Run a new Span Filter - * @param {Object} config Configuration options - */ - -Roo.htmleditor.FilterWord = function(cfg) -{ - // no need to apply config. - this.walk(cfg.node); -} - -Roo.extend(Roo.htmleditor.FilterWord, Roo.htmleditor.Filter, -{ - tag: true, - - - /** - * Clean up MS wordisms... - */ - replaceTag : function(node) - { - - // no idea what this does - span with text, replaceds with just text. - if( - node.nodeName == 'SPAN' && - !node.hasAttributes() && - node.childNodes.length == 1 && - node.firstChild.nodeName == "#text" - ) { - var textNode = node.firstChild; - node.removeChild(textNode); - if (node.getAttribute('lang') != 'zh-CN') { // do not space pad on chinese characters.. - node.parentNode.insertBefore(node.ownerDocument.createTextNode(" "), node); - } - node.parentNode.insertBefore(textNode, node); - if (node.getAttribute('lang') != 'zh-CN') { // do not space pad on chinese characters.. - node.parentNode.insertBefore(node.ownerDocument.createTextNode(" ") , node); - } - - node.parentNode.removeChild(node); - return false; // dont do chidren - we have remove our node - so no need to do chdhilren? - } - - - - if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) { - node.parentNode.removeChild(node); - return false; // dont do chidlren - } - //Roo.log(node.tagName); - // remove - but keep children.. - if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|v:|font)/)) { - //Roo.log('-- removed'); - while (node.childNodes.length) { - var cn = node.childNodes[0]; - node.removeChild(cn); - node.parentNode.insertBefore(cn, node); - // move node to parent - and clean it.. - this.replaceTag(cn); - } - node.parentNode.removeChild(node); - /// no need to iterate chidlren = it's got none.. - //this.iterateChildren(node, this.cleanWord); - return false; // no need to iterate children. - } - // clean styles - if (node.className.length) { - - var cn = node.className.split(/\W+/); - var cna = []; - Roo.each(cn, function(cls) { - if (cls.match(/Mso[a-zA-Z]+/)) { - return; - } - cna.push(cls); - }); - node.className = cna.length ? cna.join(' ') : ''; - if (!cna.length) { - node.removeAttribute("class"); - } - } - - if (node.hasAttribute("lang")) { - node.removeAttribute("lang"); - } - - if (node.hasAttribute("style")) { - - var styles = node.getAttribute("style").split(";"); - var nstyle = []; - Roo.each(styles, function(s) { - if (!s.match(/:/)) { - return; - } - var kv = s.split(":"); - if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) { - return; - } - // what ever is left... we allow. - nstyle.push(s); - }); - node.setAttribute("style", nstyle.length ? nstyle.join(';') : ''); - if (!nstyle.length) { - node.removeAttribute('style'); - } - } - return true; // do children - - - - } -}); -/** - * @class Roo.htmleditor.FilterStyleToTag - * part of the word stuff... - certain 'styles' should be converted to tags. - * eg. - * font-weight: bold -> bold - * ?? super / subscrit etc.. - * - * @constructor -* Run a new style to tag filter. -* @param {Object} config Configuration options - */ -Roo.htmleditor.FilterStyleToTag = function(cfg) -{ - - this.tags = { - B : [ 'fontWeight' , 'bold'], - I : [ 'fontStyle' , 'italic'], - //pre : [ 'font-style' , 'italic'], - // h1.. h6 ?? font-size? - SUP : [ 'verticalAlign' , 'super' ], - SUB : [ 'verticalAlign' , 'sub' ] - - - }; - - Roo.apply(this, cfg); - - - this.walk(cfg.node); - - - -} - - -Roo.extend(Roo.htmleditor.FilterStyleToTag, Roo.htmleditor.Filter, -{ - tag: true, // all tags - - tags : false, - - - replaceTag : function(node) - { - - - if (node.getAttribute("style") === null) { - return true; - } - var inject = []; - for (var k in this.tags) { - if (node.style[this.tags[k][0]] == this.tags[k][1]) { - inject.push(k); - node.style.removeProperty(this.tags[k][0]); - } - } - if (!inject.length) { - return true; - } - var cn = Array.from(node.childNodes); - var nn = node; - Roo.each(inject, function(t) { - var nc = node.ownerDocument.createElement(t); - nn.appendChild(nc); - nn = nc; - }); - for(var i = 0;i < cn.length;cn++) { - node.removeChild(cn[i]); - nn.appendChild(cn[i]); - } - return true /// iterate thru - } - -})/** - * @class Roo.htmleditor.FilterLongBr - * BR/BR/BR - keep a maximum of 2... - * @constructor - * Run a new Long BR Filter - * @param {Object} config Configuration options - */ - -Roo.htmleditor.FilterLongBr = function(cfg) -{ - // no need to apply config. - this.walk(cfg.node); -} - -Roo.extend(Roo.htmleditor.FilterLongBr, Roo.htmleditor.Filter, -{ - - - tag : 'BR', - - - replaceTag : function(node) - { - - var ps = node.nextSibling; - while (ps && ps.nodeType == 3 && ps.nodeValue.trim().length < 1) { - ps = ps.nextSibling; - } - - if (!ps && [ 'TD', 'TH', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ].indexOf(node.parentNode.tagName) > -1) { - node.parentNode.removeChild(node); // remove last BR inside one fo these tags - return false; - } - - if (!ps || ps.nodeType != 1) { - return false; - } - - if (!ps || ps.tagName != 'BR') { - - return false; - } - - - - - - if (!node.previousSibling) { - return false; - } - var ps = node.previousSibling; - - while (ps && ps.nodeType == 3 && ps.nodeValue.trim().length < 1) { - ps = ps.previousSibling; - } - if (!ps || ps.nodeType != 1) { - return false; - } - // if header or BR before.. then it's a candidate for removal.. - as we only want '2' of these.. - if (!ps || [ 'BR', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ].indexOf(ps.tagName) < 0) { - return false; - } - - node.parentNode.removeChild(node); // remove me... - - return false; // no need to do children - - } - -}); - -/** - * @class Roo.htmleditor.FilterBlock - * removes id / data-block and contenteditable that are associated with blocks - * usage should be done on a cloned copy of the dom - * @constructor -* Run a new Attribute Filter { node : xxxx }} -* @param {Object} config Configuration options - */ -Roo.htmleditor.FilterBlock = function(cfg) -{ - Roo.apply(this, cfg); - var qa = cfg.node.querySelectorAll; - this.removeAttributes('data-block'); - this.removeAttributes('contenteditable'); - this.removeAttributes('id'); - -} - -Roo.apply(Roo.htmleditor.FilterBlock.prototype, -{ - node: true, // all tags - - - removeAttributes : function(attr) - { - var ar = this.node.querySelectorAll('*[' + attr + ']'); - for (var i =0;itext

')); - * @method serialize - * @param {DomElement} node Node instance to serialize. - * @return {String} String with HTML based on DOM tree. - */ - serialize : function(node) { - - // = settings.validate; - var writer = this.writer; - var self = this; - this.handlers = { - // #text - 3: function(node) { - - writer.text(node.nodeValue, node); - }, - // #comment - 8: function(node) { - writer.comment(node.nodeValue); - }, - // Processing instruction - 7: function(node) { - writer.pi(node.name, node.nodeValue); - }, - // Doctype - 10: function(node) { - writer.doctype(node.nodeValue); - }, - // CDATA - 4: function(node) { - writer.cdata(node.nodeValue); - }, - // Document fragment - 11: function(node) { - node = node.firstChild; - if (!node) { - return; - } - while(node) { - self.walk(node); - node = node.nextSibling - } - } - }; - writer.reset(); - 1 != node.nodeType || this.inner ? this.handlers[11](node) : this.walk(node); - return writer.getContent(); - }, - - walk: function(node) - { - var attrName, attrValue, sortedAttrs, i, l, elementRule, - handler = this.handlers[node.nodeType]; - - if (handler) { - handler(node); - return; - } - - var name = node.nodeName; - var isEmpty = node.childNodes.length < 1; - - var writer = this.writer; - var attrs = node.attributes; - // Sort attributes - - writer.start(node.nodeName, attrs, isEmpty, node); - if (isEmpty) { - return; - } - node = node.firstChild; - if (!node) { - writer.end(name); - return; - } - while (node) { - this.walk(node); - node = node.nextSibling; - } - writer.end(name); - - - } - // Serialize element and treat all non elements as fragments - -}; - -/*** - * This is based loosely on tinymce - * @class Roo.htmleditor.TidyWriter - * https://github.com/thorn0/tinymce.html/blob/master/tinymce.html.js - * - * Known issues? - * - not tested much with 'PRE' formated elements. - * - * - * - */ - -Roo.htmleditor.TidyWriter = function(settings) -{ - - // indent, indentBefore, indentAfter, encode, htmlOutput, html = []; - Roo.apply(this, settings); - this.html = []; - this.state = []; - - this.encode = Roo.htmleditor.TidyEntities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); - -} -Roo.htmleditor.TidyWriter.prototype = { - - - state : false, - - indent : ' ', - - // part of state... - indentstr : '', - in_pre: false, - in_inline : false, - last_inline : false, - encode : false, - - - /** - * Writes the a start element such as

. - * - * @method start - * @param {String} name Name of the element. - * @param {Array} attrs Optional attribute array or undefined if it hasn't any. - * @param {Boolean} empty Optional empty state if the tag should end like
. - */ - start: function(name, attrs, empty, node) - { - var i, l, attr, value; - - // there are some situations where adding line break && indentation will not work. will not work. - // -1; - var in_pre = this.in_pre || Roo.htmleditor.TidyWriter.whitespace_elements.indexOf(name) > -1; - - var is_short = empty ? Roo.htmleditor.TidyWriter.shortend_elements.indexOf(name) > -1 : false; - - var add_lb = name == 'BR' ? false : in_inline; - - if (!add_lb && !this.in_pre && this.lastElementEndsWS()) { - i_inline = false; - } - - var indentstr = this.indentstr; - - // e_inline = elements that can be inline, but still allow \n before and after? - // only 'BR' ??? any others? - - // ADD LINE BEFORE tage - if (!this.in_pre) { - if (in_inline) { - //code - if (name == 'BR') { - this.addLine(); - } else if (this.lastElementEndsWS()) { - this.addLine(); - } else{ - // otherwise - no new line. (and dont indent.) - indentstr = ''; - } - - } else { - this.addLine(); - } - } else { - indentstr = ''; - } - - this.html.push(indentstr + '<', name.toLowerCase()); - - if (attrs) { - for (i = 0, l = attrs.length; i < l; i++) { - attr = attrs[i]; - this.html.push(' ', attr.name, '="', this.encode(attr.value, true), '"'); - } - } - - if (empty) { - if (is_short) { - this.html[this.html.length] = '/>'; - } else { - this.html[this.html.length] = '>'; - } - var e_inline = name == 'BR' ? false : this.in_inline; - - if (!e_inline && !this.in_pre) { - this.addLine(); - } - return; - - } - // not empty.. - this.html[this.html.length] = '>'; - - // there is a special situation, where we need to turn on in_inline - if any of the imediate chidlren are one of these. - /* - if (!in_inline && !in_pre) { - var cn = node.firstChild; - while(cn) { - if (Roo.htmleditor.TidyWriter.inline_elements.indexOf(cn.nodeName) > -1) { - in_inline = true - break; - } - cn = cn.nextSibling; - } - - } - */ - - - this.pushState({ - indentstr : in_pre ? '' : (this.indentstr + this.indent), - in_pre : in_pre, - in_inline : in_inline - }); - // add a line after if we are not in a - - if (!in_inline && !in_pre) { - this.addLine(); - } - - - - - }, - - lastElementEndsWS : function() - { - var value = this.html.length > 0 ? this.html[this.html.length-1] : false; - if (value === false) { - return true; - } - return value.match(/\s+$/); - - }, - - /** - * Writes the a end element such as

. - * - * @method end - * @param {String} name Name of the element. - */ - end: function(name) { - var value; - this.popState(); - var indentstr = ''; - var in_inline = this.in_inline || Roo.htmleditor.TidyWriter.inline_elements.indexOf(name) > -1; - - if (!this.in_pre && !in_inline) { - this.addLine(); - indentstr = this.indentstr; - } - this.html.push(indentstr + ''); - this.last_inline = in_inline; - - // pop the indent state.. - }, - /** - * Writes a text node. - * - * In pre - we should not mess with the contents. - * - * - * @method text - * @param {String} text String to write out. - * @param {Boolean} raw Optional raw state if true the contents wont get encoded. - */ - text: function(text, node) - { - // if not in whitespace critical - if (text.length < 1) { - return; - } - if (this.in_pre) { - this.html[this.html.length] = text; - return; - } - - if (this.in_inline) { - text = text.replace(/\s+/g,' '); // all white space inc line breaks to a slingle' ' - if (text != ' ') { - text = text.replace(/\s+/,' '); // all white space to single white space - - - // if next tag is '
', then we can trim right.. - if (node.nextSibling && - node.nextSibling.nodeType == 1 && - node.nextSibling.nodeName == 'BR' ) - { - text = text.replace(/\s+$/g,''); - } - // if previous tag was a BR, we can also trim.. - if (node.previousSibling && - node.previousSibling.nodeType == 1 && - node.previousSibling.nodeName == 'BR' ) - { - text = this.indentstr + text.replace(/^\s+/g,''); - } - if (text.match(/\n/)) { - text = text.replace( - /(?![^\n]{1,64}$)([^\n]{1,64})\s/g, '$1\n' + this.indentstr - ); - // remoeve the last whitespace / line break. - text = text.replace(/\n\s+$/,''); - } - // repace long lines - - } - - this.html[this.html.length] = text; - return; - } - // see if previous element was a inline element. - var indentstr = this.indentstr; - - text = text.replace(/\s+/g," "); // all whitespace into single white space. - - // should trim left? - if (node.previousSibling && - node.previousSibling.nodeType == 1 && - Roo.htmleditor.TidyWriter.inline_elements.indexOf(node.previousSibling.nodeName) > -1) - { - indentstr = ''; - - } else { - this.addLine(); - text = text.replace(/^\s+/,''); // trim left - - } - // should trim right? - if (node.nextSibling && - node.nextSibling.nodeType == 1 && - Roo.htmleditor.TidyWriter.inline_elements.indexOf(node.nextSibling.nodeName) > -1) - { - // noop - - } else { - text = text.replace(/\s+$/,''); // trim right - } - - - - - - if (text.length < 1) { - return; - } - if (!text.match(/\n/)) { - this.html.push(indentstr + text); - return; - } - - text = this.indentstr + text.replace( - /(?![^\n]{1,64}$)([^\n]{1,64})\s/g, '$1\n' + this.indentstr - ); - // remoeve the last whitespace / line break. - text = text.replace(/\s+$/,''); - - this.html.push(text); - - // split and indent.. - - - }, - /** - * Writes a cdata node such as . - * - * @method cdata - * @param {String} text String to write out inside the cdata. - */ - cdata: function(text) { - this.html.push(''); - }, - /** - * Writes a comment node such as . - * - * @method cdata - * @param {String} text String to write out inside the comment. - */ - comment: function(text) { - this.html.push(''); - }, - /** - * Writes a PI node such as . - * - * @method pi - * @param {String} name Name of the pi. - * @param {String} text String to write out inside the pi. - */ - pi: function(name, text) { - text ? this.html.push('') : this.html.push(''); - this.indent != '' && this.html.push('\n'); - }, - /** - * Writes a doctype node such as . - * - * @method doctype - * @param {String} text String to write out inside the doctype. - */ - doctype: function(text) { - this.html.push('', this.indent != '' ? '\n' : ''); - }, - /** - * Resets the internal buffer if one wants to reuse the writer. - * - * @method reset - */ - reset: function() { - this.html.length = 0; - this.state = []; - this.pushState({ - indentstr : '', - in_pre : false, - in_inline : false - }) - }, - /** - * Returns the contents that got serialized. - * - * @method getContent - * @return {String} HTML contents that got written down. - */ - getContent: function() { - return this.html.join('').replace(/\n$/, ''); - }, - - pushState : function(cfg) - { - this.state.push(cfg); - Roo.apply(this, cfg); - }, - - popState : function() - { - if (this.state.length < 1) { - return; // nothing to push - } - var cfg = { - in_pre: false, - indentstr : '' - }; - this.state.pop(); - if (this.state.length > 0) { - cfg = this.state[this.state.length-1]; - } - Roo.apply(this, cfg); - }, - - addLine: function() - { - if (this.html.length < 1) { - return; - } - - - var value = this.html[this.html.length - 1]; - if (value.length > 0 && '\n' !== value) { - this.html.push('\n'); - } - } - - -//'pre script noscript style textarea video audio iframe object code' -// shortended... 'area base basefont br col frame hr img input isindex link meta param embed source wbr track'); -// inline -}; - -Roo.htmleditor.TidyWriter.inline_elements = [ - 'SPAN','STRONG','B','EM','I','FONT','STRIKE','U','VAR', - 'CITE','DFN','CODE','MARK','Q','SUP','SUB','SAMP', 'A' -]; -Roo.htmleditor.TidyWriter.shortend_elements = [ - 'AREA','BASE','BASEFONT','BR','COL','FRAME','HR','IMG','INPUT', - 'ISINDEX','LINK','','META','PARAM','EMBED','SOURCE','WBR','TRACK' -]; - -Roo.htmleditor.TidyWriter.whitespace_elements = [ - 'PRE','SCRIPT','NOSCRIPT','STYLE','TEXTAREA','VIDEO','AUDIO','IFRAME','OBJECT','CODE' -];/*** - * This is based loosely on tinymce - * @class Roo.htmleditor.TidyEntities - * @static - * https://github.com/thorn0/tinymce.html/blob/master/tinymce.html.js - * - * Not 100% sure this is actually used or needed. - */ - -Roo.htmleditor.TidyEntities = { - - /** - * initialize data.. - */ - init : function (){ - - this.namedEntities = this.buildEntitiesLookup(this.namedEntitiesData, 32); - - }, - - - buildEntitiesLookup: function(items, radix) { - var i, chr, entity, lookup = {}; - if (!items) { - return {}; - } - items = typeof(items) == 'string' ? items.split(',') : items; - radix = radix || 10; - // Build entities lookup table - for (i = 0; i < items.length; i += 2) { - chr = String.fromCharCode(parseInt(items[i], radix)); - // Only add non base entities - if (!this.baseEntities[chr]) { - entity = '&' + items[i + 1] + ';'; - lookup[chr] = entity; - lookup[entity] = chr; - } - } - return lookup; - - }, - - asciiMap : { - 128: '€', - 130: '‚', - 131: 'ƒ', - 132: '„', - 133: '…', - 134: '†', - 135: '‡', - 136: 'ˆ', - 137: '‰', - 138: 'Š', - 139: '‹', - 140: 'Œ', - 142: 'Ž', - 145: '‘', - 146: '’', - 147: '“', - 148: '”', - 149: '•', - 150: '–', - 151: '—', - 152: '˜', - 153: '™', - 154: 'š', - 155: '›', - 156: 'œ', - 158: 'ž', - 159: 'Ÿ' - }, - // Raw entities - baseEntities : { - '"': '"', - // Needs to be escaped since the YUI compressor would otherwise break the code - '\'': ''', - '<': '<', - '>': '>', - '&': '&', - '`': '`' - }, - // Reverse lookup table for raw entities - reverseEntities : { - '<': '<', - '>': '>', - '&': '&', - '"': '"', - ''': '\'' - }, - - attrsCharsRegExp : /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, - textCharsRegExp : /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, - rawCharsRegExp : /[<>&\"\']/g, - entityRegExp : /&#([a-z0-9]+);?|&([a-z0-9]+);/gi, - namedEntities : false, - namedEntitiesData : [ - '50', - 'nbsp', - '51', - 'iexcl', - '52', - 'cent', - '53', - 'pound', - '54', - 'curren', - '55', - 'yen', - '56', - 'brvbar', - '57', - 'sect', - '58', - 'uml', - '59', - 'copy', - '5a', - 'ordf', - '5b', - 'laquo', - '5c', - 'not', - '5d', - 'shy', - '5e', - 'reg', - '5f', - 'macr', - '5g', - 'deg', - '5h', - 'plusmn', - '5i', - 'sup2', - '5j', - 'sup3', - '5k', - 'acute', - '5l', - 'micro', - '5m', - 'para', - '5n', - 'middot', - '5o', - 'cedil', - '5p', - 'sup1', - '5q', - 'ordm', - '5r', - 'raquo', - '5s', - 'frac14', - '5t', - 'frac12', - '5u', - 'frac34', - '5v', - 'iquest', - '60', - 'Agrave', - '61', - 'Aacute', - '62', - 'Acirc', - '63', - 'Atilde', - '64', - 'Auml', - '65', - 'Aring', - '66', - 'AElig', - '67', - 'Ccedil', - '68', - 'Egrave', - '69', - 'Eacute', - '6a', - 'Ecirc', - '6b', - 'Euml', - '6c', - 'Igrave', - '6d', - 'Iacute', - '6e', - 'Icirc', - '6f', - 'Iuml', - '6g', - 'ETH', - '6h', - 'Ntilde', - '6i', - 'Ograve', - '6j', - 'Oacute', - '6k', - 'Ocirc', - '6l', - 'Otilde', - '6m', - 'Ouml', - '6n', - 'times', - '6o', - 'Oslash', - '6p', - 'Ugrave', - '6q', - 'Uacute', - '6r', - 'Ucirc', - '6s', - 'Uuml', - '6t', - 'Yacute', - '6u', - 'THORN', - '6v', - 'szlig', - '70', - 'agrave', - '71', - 'aacute', - '72', - 'acirc', - '73', - 'atilde', - '74', - 'auml', - '75', - 'aring', - '76', - 'aelig', - '77', - 'ccedil', - '78', - 'egrave', - '79', - 'eacute', - '7a', - 'ecirc', - '7b', - 'euml', - '7c', - 'igrave', - '7d', - 'iacute', - '7e', - 'icirc', - '7f', - 'iuml', - '7g', - 'eth', - '7h', - 'ntilde', - '7i', - 'ograve', - '7j', - 'oacute', - '7k', - 'ocirc', - '7l', - 'otilde', - '7m', - 'ouml', - '7n', - 'divide', - '7o', - 'oslash', - '7p', - 'ugrave', - '7q', - 'uacute', - '7r', - 'ucirc', - '7s', - 'uuml', - '7t', - 'yacute', - '7u', - 'thorn', - '7v', - 'yuml', - 'ci', - 'fnof', - 'sh', - 'Alpha', - 'si', - 'Beta', - 'sj', - 'Gamma', - 'sk', - 'Delta', - 'sl', - 'Epsilon', - 'sm', - 'Zeta', - 'sn', - 'Eta', - 'so', - 'Theta', - 'sp', - 'Iota', - 'sq', - 'Kappa', - 'sr', - 'Lambda', - 'ss', - 'Mu', - 'st', - 'Nu', - 'su', - 'Xi', - 'sv', - 'Omicron', - 't0', - 'Pi', - 't1', - 'Rho', - 't3', - 'Sigma', - 't4', - 'Tau', - 't5', - 'Upsilon', - 't6', - 'Phi', - 't7', - 'Chi', - 't8', - 'Psi', - 't9', - 'Omega', - 'th', - 'alpha', - 'ti', - 'beta', - 'tj', - 'gamma', - 'tk', - 'delta', - 'tl', - 'epsilon', - 'tm', - 'zeta', - 'tn', - 'eta', - 'to', - 'theta', - 'tp', - 'iota', - 'tq', - 'kappa', - 'tr', - 'lambda', - 'ts', - 'mu', - 'tt', - 'nu', - 'tu', - 'xi', - 'tv', - 'omicron', - 'u0', - 'pi', - 'u1', - 'rho', - 'u2', - 'sigmaf', - 'u3', - 'sigma', - 'u4', - 'tau', - 'u5', - 'upsilon', - 'u6', - 'phi', - 'u7', - 'chi', - 'u8', - 'psi', - 'u9', - 'omega', - 'uh', - 'thetasym', - 'ui', - 'upsih', - 'um', - 'piv', - '812', - 'bull', - '816', - 'hellip', - '81i', - 'prime', - '81j', - 'Prime', - '81u', - 'oline', - '824', - 'frasl', - '88o', - 'weierp', - '88h', - 'image', - '88s', - 'real', - '892', - 'trade', - '89l', - 'alefsym', - '8cg', - 'larr', - '8ch', - 'uarr', - '8ci', - 'rarr', - '8cj', - 'darr', - '8ck', - 'harr', - '8dl', - 'crarr', - '8eg', - 'lArr', - '8eh', - 'uArr', - '8ei', - 'rArr', - '8ej', - 'dArr', - '8ek', - 'hArr', - '8g0', - 'forall', - '8g2', - 'part', - '8g3', - 'exist', - '8g5', - 'empty', - '8g7', - 'nabla', - '8g8', - 'isin', - '8g9', - 'notin', - '8gb', - 'ni', - '8gf', - 'prod', - '8gh', - 'sum', - '8gi', - 'minus', - '8gn', - 'lowast', - '8gq', - 'radic', - '8gt', - 'prop', - '8gu', - 'infin', - '8h0', - 'ang', - '8h7', - 'and', - '8h8', - 'or', - '8h9', - 'cap', - '8ha', - 'cup', - '8hb', - 'int', - '8hk', - 'there4', - '8hs', - 'sim', - '8i5', - 'cong', - '8i8', - 'asymp', - '8j0', - 'ne', - '8j1', - 'equiv', - '8j4', - 'le', - '8j5', - 'ge', - '8k2', - 'sub', - '8k3', - 'sup', - '8k4', - 'nsub', - '8k6', - 'sube', - '8k7', - 'supe', - '8kl', - 'oplus', - '8kn', - 'otimes', - '8l5', - 'perp', - '8m5', - 'sdot', - '8o8', - 'lceil', - '8o9', - 'rceil', - '8oa', - 'lfloor', - '8ob', - 'rfloor', - '8p9', - 'lang', - '8pa', - 'rang', - '9ea', - 'loz', - '9j0', - 'spades', - '9j3', - 'clubs', - '9j5', - 'hearts', - '9j6', - 'diams', - 'ai', - 'OElig', - 'aj', - 'oelig', - 'b0', - 'Scaron', - 'b1', - 'scaron', - 'bo', - 'Yuml', - 'm6', - 'circ', - 'ms', - 'tilde', - '802', - 'ensp', - '803', - 'emsp', - '809', - 'thinsp', - '80c', - 'zwnj', - '80d', - 'zwj', - '80e', - 'lrm', - '80f', - 'rlm', - '80j', - 'ndash', - '80k', - 'mdash', - '80o', - 'lsquo', - '80p', - 'rsquo', - '80q', - 'sbquo', - '80s', - 'ldquo', - '80t', - 'rdquo', - '80u', - 'bdquo', - '810', - 'dagger', - '811', - 'Dagger', - '81g', - 'permil', - '81p', - 'lsaquo', - '81q', - 'rsaquo', - '85c', - 'euro' - ], - - - /** - * Encodes the specified string using raw entities. This means only the required XML base entities will be encoded. - * - * @method encodeRaw - * @param {String} text Text to encode. - * @param {Boolean} attr Optional flag to specify if the text is attribute contents. - * @return {String} Entity encoded text. - */ - encodeRaw: function(text, attr) - { - var t = this; - return text.replace(attr ? this.attrsCharsRegExp : this.textCharsRegExp, function(chr) { - return t.baseEntities[chr] || chr; - }); - }, - /** - * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents - * since it doesn't know if the context is within a attribute or text node. This was added for compatibility - * and is exposed as the DOMUtils.encode function. - * - * @method encodeAllRaw - * @param {String} text Text to encode. - * @return {String} Entity encoded text. - */ - encodeAllRaw: function(text) { - var t = this; - return ('' + text).replace(this.rawCharsRegExp, function(chr) { - return t.baseEntities[chr] || chr; - }); - }, - /** - * Encodes the specified string using numeric entities. The core entities will be - * encoded as named ones but all non lower ascii characters will be encoded into numeric entities. - * - * @method encodeNumeric - * @param {String} text Text to encode. - * @param {Boolean} attr Optional flag to specify if the text is attribute contents. - * @return {String} Entity encoded text. - */ - encodeNumeric: function(text, attr) { - var t = this; - return text.replace(attr ? this.attrsCharsRegExp : this.textCharsRegExp, function(chr) { - // Multi byte sequence convert it to a single entity - if (chr.length > 1) { - return '&#' + (1024 * (chr.charCodeAt(0) - 55296) + (chr.charCodeAt(1) - 56320) + 65536) + ';'; - } - return t.baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';'; - }); - }, - /** - * Encodes the specified string using named entities. The core entities will be encoded - * as named ones but all non lower ascii characters will be encoded into named entities. - * - * @method encodeNamed - * @param {String} text Text to encode. - * @param {Boolean} attr Optional flag to specify if the text is attribute contents. - * @param {Object} entities Optional parameter with entities to use. - * @return {String} Entity encoded text. - */ - encodeNamed: function(text, attr, entities) { - var t = this; - entities = entities || this.namedEntities; - return text.replace(attr ? this.attrsCharsRegExp : this.textCharsRegExp, function(chr) { - return t.baseEntities[chr] || entities[chr] || chr; - }); - }, - /** - * Returns an encode function based on the name(s) and it's optional entities. - * - * @method getEncodeFunc - * @param {String} name Comma separated list of encoders for example named,numeric. - * @param {String} entities Optional parameter with entities to use instead of the built in set. - * @return {function} Encode function to be used. - */ - getEncodeFunc: function(name, entities) { - entities = this.buildEntitiesLookup(entities) || this.namedEntities; - var t = this; - function encodeNamedAndNumeric(text, attr) { - return text.replace(attr ? t.attrsCharsRegExp : t.textCharsRegExp, function(chr) { - return t.baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr; - }); - } - - function encodeCustomNamed(text, attr) { - return t.encodeNamed(text, attr, entities); - } - // Replace + with , to be compatible with previous TinyMCE versions - name = this.makeMap(name.replace(/\+/g, ',')); - // Named and numeric encoder - if (name.named && name.numeric) { - return this.encodeNamedAndNumeric; - } - // Named encoder - if (name.named) { - // Custom names - if (entities) { - return encodeCustomNamed; - } - return this.encodeNamed; - } - // Numeric - if (name.numeric) { - return this.encodeNumeric; - } - // Raw encoder - return this.encodeRaw; - }, - /** - * Decodes the specified string, this will replace entities with raw UTF characters. - * - * @method decode - * @param {String} text Text to entity decode. - * @return {String} Entity decoded string. - */ - decode: function(text) - { - var t = this; - return text.replace(this.entityRegExp, function(all, numeric) { - if (numeric) { - numeric = 'x' === numeric.charAt(0).toLowerCase() ? parseInt(numeric.substr(1), 16) : parseInt(numeric, 10); - // Support upper UTF - if (numeric > 65535) { - numeric -= 65536; - return String.fromCharCode(55296 + (numeric >> 10), 56320 + (1023 & numeric)); - } - return t.asciiMap[numeric] || String.fromCharCode(numeric); - } - return t.reverseEntities[all] || t.namedEntities[all] || t.nativeDecode(all); - }); - }, - nativeDecode : function (text) { - return text; - }, - makeMap : function (items, delim, map) { - var i; - items = items || []; - delim = delim || ','; - if (typeof items == "string") { - items = items.split(delim); - } - map = map || {}; - i = items.length; - while (i--) { - map[items[i]] = {}; - } - return map; - } -}; - - - -Roo.htmleditor.TidyEntities.init(); -/** - * @class Roo.htmleditor.KeyEnter - * Handle Enter press.. - * @cfg {Roo.HtmlEditorCore} core the editor. - * @constructor - * Create a new Filter. - * @param {Object} config Configuration options - */ - - - - - -Roo.htmleditor.KeyEnter = function(cfg) { - Roo.apply(this, cfg); - // this does not actually call walk as it's really just a abstract class - - Roo.get(this.core.doc.body).on('keypress', this.keypress, this); -} - -//Roo.htmleditor.KeyEnter.i = 0; - - -Roo.htmleditor.KeyEnter.prototype = { - - core : false, - - keypress : function(e) - { - if (e.charCode != 13 && e.charCode != 10) { - Roo.log([e.charCode,e]); - return true; - } - e.preventDefault(); - // https://stackoverflow.com/questions/18552336/prevent-contenteditable-adding-div-on-enter-chrome - var doc = this.core.doc; - //add a new line - - - var sel = this.core.getSelection(); - var range = sel.getRangeAt(0); - var n = range.commonAncestorContainer; - var pc = range.closest([ 'ol', 'ul']); - var pli = range.closest('li'); - if (!pc || e.ctrlKey) { - sel.insertNode('br', 'after'); - - this.core.undoManager.addEvent(); - this.core.fireEditorEvent(e); - return false; - } - - // deal with
  • 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; - - - - - - } -}; - -/** - * @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); - } - if (!n) { - return ''; - } - if (attr == 'html') { - return n.innerHTML; - } - if (attr == 'style') { - return n.style[style]; - } - - return n.hasAttribute(attr) ? n.getAttribute(attr) : ''; - - }, - /** - * create a DomHelper friendly object - for use with - * Roo.DomHelper.markup / overwrite / etc.. - * (override this) - */ - toObject : function() - { - return {}; - }, - /** - * Read a node that has a 'data-block' property - and extract the values from it. - * @param {DomElement} node - the node - */ - readElement : function(node) - { - - } - - -}; - - - -/** - * @class Roo.htmleditor.BlockFigure - * Block that has an image and a figcaption - * @cfg {String} image_src the url for the image - * @cfg {String} align (left|right) alignment for the block default left - * @cfg {String} caption the text to appear below (and in the alt tag) - * @cfg {String} caption_display (block|none) display or not the caption - * @cfg {String|number} image_width the width of the image number or %? - * @cfg {String|number} image_height the height of the image number or %? - * - * @constructor - * Create a new Filter. - * @param {Object} config Configuration options - */ - -Roo.htmleditor.BlockFigure = function(cfg) -{ - if (cfg.node) { - this.readElement(cfg.node); - this.updateElement(cfg.node); - } - Roo.apply(this, cfg); -} -Roo.extend(Roo.htmleditor.BlockFigure, Roo.htmleditor.Block, { - - - // setable values. - image_src: '', - align: 'center', - caption : '', - caption_display : 'block', - width : '100%', - cls : '', - href: '', - video_url : '', - - // margin: '2%', not used - - text_align: 'left', // (left|right) alignment for the text caption default left. - not used at present - - - // used by context menu - friendly_name : 'Image with caption', - deleteTitle : "Delete Image and Caption", - - contextMenu : function(toolbar) - { - - var block = function() { - return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode); - }; - - - var rooui = typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap; - - var syncValue = toolbar.editorcore.syncValue; - - var fields = {}; - - return [ - { - xtype : 'TextItem', - text : "Source: ", - xns : rooui.Toolbar //Boostrap? - }, - { - xtype : 'Button', - text: 'Change Image URL', - - listeners : { - click: function (btn, state) - { - var b = block(); - - Roo.MessageBox.show({ - title : "Image Source URL", - msg : "Enter the url for the image", - buttons: Roo.MessageBox.OKCANCEL, - fn: function(btn, val){ - if (btn != 'ok') { - return; - } - b.image_src = val; - b.updateElement(); - syncValue(); - toolbar.editorcore.onEditorEvent(); - }, - minWidth:250, - prompt:true, - //multiline: multiline, - modal : true, - value : b.image_src - }); - } - }, - xns : rooui.Toolbar - }, - - { - xtype : 'Button', - text: 'Change Link URL', - - listeners : { - click: function (btn, state) - { - var b = block(); - - Roo.MessageBox.show({ - title : "Link URL", - msg : "Enter the url for the link - leave blank to have no link", - buttons: Roo.MessageBox.OKCANCEL, - fn: function(btn, val){ - if (btn != 'ok') { - return; - } - b.href = val; - b.updateElement(); - syncValue(); - toolbar.editorcore.onEditorEvent(); - }, - minWidth:250, - prompt:true, - //multiline: multiline, - modal : true, - value : b.href - }); - } - }, - xns : rooui.Toolbar - }, - { - xtype : 'Button', - text: 'Show Video URL', - - listeners : { - click: function (btn, state) - { - Roo.MessageBox.alert("Video URL", - block().video_url == '' ? 'This image is not linked ot a video' : - 'The image is linked to: ' + block().video_url + ''); - } - }, - xns : rooui.Toolbar - }, - - - { - xtype : 'TextItem', - text : "Width: ", - xns : rooui.Toolbar //Boostrap? - }, - { - xtype : 'ComboBox', - allowBlank : false, - displayField : 'val', - editable : true, - listWidth : 100, - triggerAction : 'all', - typeAhead : true, - valueField : 'val', - width : 70, - name : 'width', - listeners : { - select : function (combo, r, index) - { - toolbar.editorcore.selectNode(toolbar.tb.selectedNode); - var b = block(); - b.width = r.get('val'); - b.updateElement(); - syncValue(); - toolbar.editorcore.onEditorEvent(); - } - }, - xns : rooui.form, - store : { - xtype : 'SimpleStore', - data : [ - ['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, " ").replace(/\s+/g, ' ').trim(), // removeHTML and reduce spaces.. - 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 - ] - }; - } - - var captionhtml = this.caption_display == 'hidden' ? this.caption : (this.caption.length ? this.caption : "Caption"); - - 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', - - style : { - 'text-align': 'left', - 'margin-top' : '16px', - 'font-size' : '16px', - 'line-height' : '24px', - display : this.caption_display - }, - cls : this.cls.length > 0 ? (this.cls + '-thumbnail' ) : '', - cn : [ - { - // we can not rely on yahoo syndication to use CSS elements - so have to use '' to encase stuff. - tag : 'i', - contenteditable : true, - html : captionhtml - } - ] - - } - ] - }; - - }, - - 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'); - // remove ' - if (this.caption.trim().match(/^]*>/i)) { - this.caption = this.caption.trim().replace(/^]*>/i, '').replace(/^<\/i>$/i, ''); - } - //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; - } - - - - - - - - -}) - - - -/** - * @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); - } - - - - -}) - -//