X-Git-Url: http://git.roojs.org/?p=roojs1;a=blobdiff_plain;f=roojs-bootstrap-debug.js;h=e93cd47f191a6e01a0df017d3a85b8ae24ef10ae;hp=0227f6ecf6bb451fd54b1601f9347b8fd952db60;hb=135ac93bf938512823e8371385e746a9587ed92e;hpb=41c17424eabdfca1a95ee26ad508f691a369f674 diff --git a/roojs-bootstrap-debug.js b/roojs-bootstrap-debug.js index 0227f6ecf6..e93cd47f19 100644 --- a/roojs-bootstrap-debug.js +++ b/roojs-bootstrap-debug.js @@ -26025,7 +26025,7 @@ Roo.htmleditor.Filter.prototype = { Roo.each( Array.from(dom.childNodes), function( e ) { switch(true) { - case e.nodeType == 8 && typeof(this.replaceComment) != 'undefined': // comment + case e.nodeType == 8 && this.replaceComment !== false: // comment this.replaceComment(e); return; @@ -26225,7 +26225,7 @@ Roo.extend(Roo.htmleditor.FilterBlack, Roo.htmleditor.Filter, { tag : true, // all elements. - replace : function(n) + replaceTag : function(n) { n.parentNode.removeChild(n); } @@ -26624,7 +26624,7 @@ Roo.extend(Roo.htmleditor.FilterStyleToTag, Roo.htmleditor.Filter, var cn = Array.from(node.childNodes); var nn = node; Roo.each(inject, function(t) { - var nc = node.ownerDocument.createelement(t); + var nc = node.ownerDocument.createElement(t); nn.appendChild(nc); nn = nc; }); @@ -26704,6 +26704,42 @@ Roo.extend(Roo.htmleditor.FilterLongBr, Roo.htmleditor.Filter, } +}); + +/** + * @class Roo.htmleditor.FilterBlock + * removes id / data-block and contenteditable that are associated with blocks + * usage should be done on a cloned copy of the dom + * @constructor +* Run a new Attribute Filter { node : xxxx }} +* @param {Object} config Configuration options + */ +Roo.htmleditor.FilterBlock = function(cfg) +{ + Roo.apply(this, cfg); + var qa = cfg.node.querySelectorAll; + this.removeAttributes('data-block'); + this.removeAttributes('contenteditable'); + this.removeAttributes('id'); + +} + +Roo.apply(Roo.htmleditor.FilterBlock.prototype, +{ + node: true, // all tags + + + removeAttributes : function(attr) + { + var ar = this.node.querySelectorAll('*[' + attr + ']'); + for (var i =0;i insetion + if (pli.innerText.trim() == '' && + pli.previousSibling && + pli.previousSibling.nodeName == 'LI' && + pli.previousSibling.innerText.trim() == '') { + pli.parentNode.removeChild(pli.previousSibling); + sel.cursorAfter(pc); + this.core.undoManager.addEvent(); + this.core.fireEditorEvent(e); return false; - - } - //add the br, or p, or something else - newEle = doc.createElement('br'); - docFragment.appendChild(newEle); - //make the br replace selection - - range.deleteContents(); + var li = doc.createElement('LI'); + li.innerHTML = ' '; + if (!pli || !pli.firstSibling) { + pc.appendChild(li); + } else { + pli.parentNode.insertBefore(li, pli.firstSibling); + } + sel.cursorText (li.firstChild); + + this.core.undoManager.addEvent(); + this.core.fireEditorEvent(e); + + return false; - range.insertNode(docFragment); - - //create a new range - range = doc.createRange(); - range.setStartAfter(newEle); - range.collapse(true); - - //make the cursor there - var sel = this.core.win.getSelection(); - sel.removeAllRanges(); - sel.addRange(range); - return false; + + } }; @@ -26817,24 +26842,51 @@ Roo.htmleditor.Block = function(cfg) { // do nothing .. should not be called really. } - +/** + * factory method to get the block from an element (using cache if necessary) + * @static + * @param {HtmlElement} the dom element + */ Roo.htmleditor.Block.factory = function(node) { - + var cc = Roo.htmleditor.Block.cache; var id = Roo.get(node).id; - if (typeof(Roo.htmleditor.Block.cache[id]) != 'undefined') { - Roo.htmleditor.Block.cache[id].readElement(); + if (typeof(cc[id]) != 'undefined' && (!cc[id].node || cc[id].node.closest('body'))) { + Roo.htmleditor.Block.cache[id].readElement(node); return Roo.htmleditor.Block.cache[id]; } - - var cls = Roo.htmleditor['Block' + Roo.get(node).attr('data-block')]; + var db = node.getAttribute('data-block'); + if (!db) { + db = node.nodeName.toLowerCase().toUpperCaseFirst(); + } + var cls = Roo.htmleditor['Block' + db]; if (typeof(cls) == 'undefined') { - Roo.log("OOps missing block : " + 'Block' + Roo.get(node).attr('data-block')); + //Roo.log(node.getAttribute('data-block')); + Roo.log("OOps missing block : " + 'Block' + db); return false; } Roo.htmleditor.Block.cache[id] = new cls({ node: node }); return Roo.htmleditor.Block.cache[id]; /// should trigger update element }; + +/** + * initalize all Elements from content that are 'blockable' + * @static + * @param the body element + */ +Roo.htmleditor.Block.initAll = function(body, type) +{ + if (typeof(type) == 'undefined') { + var ia = Roo.htmleditor.Block.initAll; + ia(body,'table'); + ia(body,'td'); + ia(body,'figure'); + return; + } + Roo.each(Roo.get(body).query(type), function(e) { + Roo.htmleditor.Block.factory(e); + },this); +}; // question goes here... do we need to clear out this cache sometimes? // or show we make it relivant to the htmleditor. Roo.htmleditor.Block.cache = {}; @@ -26844,7 +26896,10 @@ Roo.htmleditor.Block.prototype = { node : false, // used by context menu - friendly_name : 'Image with caption', + friendly_name : 'Based Block', + + // text for button to delete this element + deleteTitle : false, context : false, /** @@ -26879,14 +26934,17 @@ Roo.htmleditor.Block.prototype = { // but kiss for now. n = node.getElementsByTagName(tag).item(0); } + if (!n) { + return ''; + } if (attr == 'html') { return n.innerHTML; } if (attr == 'style') { - return Roo.get(n).getStyle(style); + return n.style[style]; } - return Roo.get(n).attr(attr); + return n.hasAttribute(attr) ? n.getAttribute(attr) : ''; }, /** @@ -26917,8 +26975,8 @@ Roo.htmleditor.Block.prototype = { * Block that has an image and a figcaption * @cfg {String} image_src the url for the image * @cfg {String} align (left|right) alignment for the block default left - * @cfg {String} text_align (left|right) alignment for the text caption default left. * @cfg {String} caption the text to appear below (and in the alt tag) + * @cfg {String} caption_display (block|none) display or not the caption * @cfg {String|number} image_width the width of the image number or %? * @cfg {String|number} image_height the height of the image number or %? * @@ -26927,114 +26985,1603 @@ Roo.htmleditor.Block.prototype = { * @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, { - +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 + ] + }; + } + + return { + tag: 'figure', + 'data-block' : 'Figure', + contenteditable : 'false', + style : { + display: 'block', + float : this.align , + 'max-width': this.width, + width : 'auto', + margin: m, + padding: '10px' + + }, + + + align : this.align, + cn : [ + img, + + { + tag: 'figcaption', + contenteditable : true, + style : { + 'text-align': 'left', + 'margin-top' : '16px', + 'font-size' : '16px', + 'line-height' : '24px', + 'font-style': 'italic', + display : this.caption_display + }, + cls : this.cls.length > 0 ? (this.cls + '-thumbnail' ) : '', + html : this.caption + + } + ] + }; + + }, + + readElement : function(node) + { + // this should not really come from the link... + this.video_url = this.getVal(node, 'div', 'src'); + this.cls = this.getVal(node, 'div', 'class'); + this.href = this.getVal(node, 'a', 'href'); + + this.image_src = this.getVal(node, 'img', 'src'); + + this.align = this.getVal(node, 'figure', 'align'); + this.caption = this.getVal(node, 'figcaption', 'html'); + //this.text_align = this.getVal(node, 'figcaption', 'style','text-align'); + this.width = this.getVal(node, 'figure', 'style', 'max-width'); + //this.margin = this.getVal(node, 'figure', 'style', 'margin'); + + }, + 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); + + }, - // setable values. - image_src: '', - align: 'left', - caption : '', - text_align: 'left', + 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); + + + + }, - width : '46%', - margin: '2%', - // used by context menu - friendly_name : 'Image with caption', - context : { // ?? static really - width : { - title: "Width", - width: 40 - // ?? number - }, - margin : { - title: "Margin", - width: 40 - // ?? number - }, - align: { - title: "Align", - opts : [[ "left"],[ "right"]], - width : 80 + 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){ - }, - text_align: { - title: "Caption Align", - opts : [ [ "left"],[ "right"],[ "center"]], - width : 80 - }, + 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; + } + } - - image_src : { - title: "Src", - width: 220 + }, + 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 + } } }, - /** - * create a DomHelper friendly object - for use with - * Roo.DomHelper.markup / overwrite / etc.. - */ - toObject : function() + normalizeWidths : function(table) { - var d = document.createElement('div'); - d.innerHTML = this.caption; + + if (this.colWidths[0] === false) { + var nw = 100.0 / this.colWidths.length; + this.colWidths.forEach(function(w,i) { + this.colWidths[i] = nw; + },this); + return; + } + + var t = 0, missing = []; - return { - tag: 'figure', - 'data-block' : 'Figure', - contenteditable : 'false', - style : { - display: 'table', - float : this.align , - width : this.width, - margin: this.margin - }, - cn : [ - { - tag : 'img', - src : this.image_src, - alt : d.innerText.replace(/\n/g, " "), // removeHTML.. - style: { - width: '100%' - } - }, - { - tag: 'figcaption', - contenteditable : true, - style : { - 'text-align': this.text_align - }, - html : this.caption - + 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.. + + }, - readElement : function(node) + shrinkColumn : function() { - this.image_src = this.getVal(node, 'img', 'src'); - this.align = this.getVal(node, 'figure', 'style', 'float'); - this.caption = this.getVal(node, 'figcaption', 'html'); - this.text_align = this.getVal(node, 'figcaption', 'style','text-align'); - this.width = this.getVal(node, 'figure', 'style', 'width'); - this.margin = this.getVal(node, 'figure', 'style', 'margin'); + var table = this.toTableArray(); + 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); + } @@ -27114,7 +28661,8 @@ Roo.HtmlEditorCore = function(config){ * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks. * @param {Roo.HtmlEditorCore} this */ - editorevent: true + editorevent: true + }); @@ -27150,15 +28698,30 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { * @cfg {Number} width (in pixels) */ width: 500, + /** + * @cfg {boolean} autoClean - default true - loading and saving will remove quite a bit of formating, + * if you are doing an email editor, this probably needs disabling, it's designed + */ + autoClean: true, + /** + * @cfg {boolean} enableBlocks - default true - if the block editor (table and figure should be enabled) + */ + enableBlocks : true, /** * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets. * */ stylesheets: false, + /** + * @cfg {String} language default en - language of text (usefull for rtl languages) + * + */ + language: 'en', /** - * @cfg {boolean} allowComments - default false - allow comments in HTML source - by default they are stripped - if you are editing email you may need this. + * @cfg {boolean} allowComments - default false - allow comments in HTML source + * - by default they are stripped - if you are editing email you may need this. */ allowComments: false, // id of frame.. @@ -27222,14 +28785,16 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { st += ''; - - var cls = 'roo-htmleditor-body'; + + st += ''; + + var cls = 'notranslate roo-htmleditor-body'; if(this.bodyCls.length){ cls += ' ' + this.bodyCls; } - return '' + st + + return '' + st + //' + @@ -27356,7 +28921,8 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { * @param {String} html The HTML to be cleaned * return {String} The cleaned HTML */ - cleanHtml : function(html){ + cleanHtml : function(html) + { html = String(html); if(html.length > 5){ if(Roo.isSafari){ // strip safari nonsense @@ -27376,33 +28942,36 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { */ syncValue : function() { - Roo.log("HtmlEditorCore:syncValue (EDITOR->TEXT)"); + //Roo.log("HtmlEditorCore:syncValue (EDITOR->TEXT)"); if(this.initialized){ this.undoManager.addEvent(); var bd = (this.doc.body || this.doc.documentElement); - //this.cleanUpPaste(); -- this is done else where and causes havoc.. - - // not sure if this is really the place for this - // the blocks are synced occasionaly - since we currently dont add listeners on the blocks - // this has to update attributes that get duped.. like alt and caption.. - - - //Roo.each(Roo.get(this.doc.body).query('*[data-block]'), function(e) { - // Roo.htmleditor.Block.factory(e); - //},this); + + var sel = this.win.getSelection(); var div = document.createElement('div'); div.innerHTML = bd.innerHTML; - // remove content editable. (blocks) + var gtx = div.getElementsByClassName('gtx-trans-icon'); // google translate - really annoying and difficult to get rid of. + if (gtx.length > 0) { + var rm = gtx.item(0).parentNode; + rm.parentNode.removeChild(rm); + } - new Roo.htmleditor.FilterAttributes({node : div, attrib_black: [ 'contenteditable' ] }); + if (this.enableBlocks) { + new Roo.htmleditor.FilterBlock({ node : div }); + } //?? tidy? - var html = div.innerHTML; + var tidy = new Roo.htmleditor.TidySerializer({ + inner: true + }); + var html = tidy.serialize(div); + + if(Roo.isSafari){ var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element! var m = bs ? bs.match(/text-align:(.*?);/i) : false; @@ -27453,7 +29022,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { */ pushValue : function() { - Roo.log("HtmlEditorCore:pushValue (TEXT->EDITOR)"); + //Roo.log("HtmlEditorCore:pushValue (TEXT->EDITOR)"); if(this.initialized){ var v = this.el.dom.value.trim(); @@ -27465,12 +29034,14 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { this.el.dom.value = d.innerHTML; this.owner.fireEvent('push', this, v); } + if (this.autoClean) { + new Roo.htmleditor.FilterParagraph({node : this.doc.body}); // paragraphs + new Roo.htmleditor.FilterSpan({node : this.doc.body}); // empty spans + } + + Roo.htmleditor.Block.initAll(this.doc.body); + this.updateLanguage(); - Roo.each(Roo.get(this.doc.body).query('*[data-block]'), function(e) { - - Roo.htmleditor.Block.factory(e); - - },this); var lc = this.doc.body.lastChild; if (lc && lc.nodeType == 1 && lc.getAttribute("contenteditable") == "false") { // add an extra line at the end. @@ -27539,9 +29110,11 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { //var ss = this.el.getStyles( 'background-image', 'background-repeat'); //ss['background-attachment'] = 'fixed'; // w3c dbody.bgProperties = 'fixed'; // ie + dbody.setAttribute("translate", "no"); + //Roo.DomHelper.applyStyles(dbody, ss); Roo.EventManager.on(this.doc, { - //'mousedown': this.onEditorEvent, + 'mouseup': this.onEditorEvent, 'dblclick': this.onEditorEvent, 'click': this.onEditorEvent, @@ -27557,6 +29130,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { if(Roo.isGecko){ Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this); } + //??? needed??? if(Roo.isIE || Roo.isSafari || Roo.isOpera){ Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this); } @@ -27571,7 +29145,8 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { this.owner.fireEvent('initialize', this); this.pushValue(); }, - + // this is to prevent a href clicks resulting in a redirect? + onPasteEvent : function(e,v) { // I think we better assume paste is going to be a dirty load of rubish from word.. @@ -27605,29 +29180,58 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { var d = (new DOMParser().parseFromString(html, 'text/html')).body; + + var sn = this.getParentElement(); + // check if d contains a table, and prevent nesting?? + //Roo.log(d.getElementsByTagName('table')); + //Roo.log(sn); + //Roo.log(sn.closest('table')); + if (d.getElementsByTagName('table').length && sn && sn.closest('table')) { + e.preventDefault(); + this.insertAtCursor("You can not nest tables"); + //Roo.log("prevent?"); // fixme - + return false; + } + if (images.length > 0) { Roo.each(d.getElementsByTagName('img'), function(img, i) { img.setAttribute('src', images[i]); }); } - - - new Roo.htmleditor.FilterStyleToTag({ node : d }); - new Roo.htmleditor.FilterAttributes({ - node : d, - attrib_white : ['href', 'src', 'name', 'align'], - attrib_clean : ['href', 'src' ] - }); - new Roo.htmleditor.FilterBlack({ node : d, tag : this.black}); - // should be fonts.. - new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT' ]} ); - new Roo.htmleditor.FilterParagraph({ node : d }); - new Roo.htmleditor.FilterSpan({ node : d }); - new Roo.htmleditor.FilterLongBr({ node : d }); + if (this.autoClean) { + new Roo.htmleditor.FilterStyleToTag({ node : d }); + new Roo.htmleditor.FilterAttributes({ + node : d, + attrib_white : ['href', 'src', 'name', 'align'], + attrib_clean : ['href', 'src' ] + }); + new Roo.htmleditor.FilterBlack({ node : d, tag : this.black}); + // should be fonts.. + new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT', 'O:P' ]} ); + new Roo.htmleditor.FilterParagraph({ node : d }); + new Roo.htmleditor.FilterSpan({ node : d }); + new Roo.htmleditor.FilterLongBr({ node : d }); + } + if (this.enableBlocks) { + + Array.from(d.getElementsByTagName('img')).forEach(function(img) { + if (img.closest('figure')) { // assume!! that it's aready + return; + } + var fig = new Roo.htmleditor.BlockFigure({ + image_src : img.src + }); + fig.updateElement(img); // replace it.. + + }); + } + this.insertAtCursor(d.innerHTML.replace(/ /g,' ')); + if (this.enableBlocks) { + Roo.htmleditor.Block.initAll(this.doc.body); + } - this.insertAtCursor(d.innerHTML); e.preventDefault(); return false; @@ -27701,10 +29305,48 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { onEditorEvent : function(e) { - this.owner.fireEvent('editorevent', this, e); + + + if (e && (e.ctrlKey || e.metaKey) && e.keyCode === 90) { + return; // we do not handle this.. (undo manager does..) + } + // in theory this detects if the last element is not a br, then we try and do that. + // its so clicking in space at bottom triggers adding a br and moving the cursor. + if (e && + e.target.nodeName == 'BODY' && + e.type == "mouseup" && + this.doc.body.lastChild + ) { + var lc = this.doc.body.lastChild; + // gtx-trans is google translate plugin adding crap. + while ((lc.nodeType == 3 && lc.nodeValue == '') || lc.id == 'gtx-trans') { + lc = lc.previousSibling; + } + if (lc.nodeType == 1 && lc.nodeName != 'BR') { + // if last element is
- then dont do anything. + + var ns = this.doc.createElement('br'); + this.doc.body.appendChild(ns); + range = this.doc.createRange(); + range.setStartAfter(ns); + range.collapse(true); + var sel = this.win.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + } + } + + + + this.fireEditorEvent(e); // this.updateToolbar(); this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff }, + + fireEditorEvent: function(e) + { + this.owner.fireEvent('editorevent', this, e); + }, insertTag : function(tg) { @@ -27749,7 +29391,37 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { * @param {String} cmd The Midas command * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null) */ - relayCmd : function(cmd, value){ + relayCmd : function(cmd, value) + { + + switch (cmd) { + case 'justifyleft': + case 'justifyright': + case 'justifycenter': + // if we are in a cell, then we will adjust the + var n = this.getParentElement(); + var td = n.closest('td'); + if (td) { + var bl = Roo.htmleditor.Block.factory(td); + bl.textAlign = cmd.replace('justify',''); + bl.updateElement(); + this.owner.fireEvent('editorevent', this); + return; + } + this.execCmd('styleWithCSS', true); // + break; + case 'bold': + case 'italic': + // if there is no selection, then we insert, and set the curson inside it.. + this.execCmd('styleWithCSS', false); + break; + + + default: + break; + } + + this.win.focus(); this.execCmd(cmd, value); this.owner.fireEvent('editorevent', this); @@ -27847,9 +29519,11 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { } if(cmd){ - this.win.focus(); - this.execCmd(cmd); - this.deferFocus(); + + this.relayCmd(cmd); + //this.win.focus(); + //this.execCmd(cmd); + //this.deferFocus(); e.preventDefault(); } @@ -27859,6 +29533,8 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { // private fixKeys : function(){ // load time branching for fastest keydown performance + + if(Roo.isIE){ return function(e){ var k = e.getKey(), r; @@ -27872,7 +29548,8 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { } return; } - + /// this is handled by Roo.htmleditor.KeyEnter + /* if(k == e.ENTER){ r = this.doc.selection.createRange(); if(r){ @@ -27885,6 +29562,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { } } } + */ //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste // this.cleanUpPaste.defer(100, this); // return; @@ -27901,6 +29579,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { this.execCmd('InsertHTML','    '); this.deferFocus(); } + //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste // this.cleanUpPaste.defer(100, this); // return; @@ -27917,6 +29596,8 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { this.deferFocus(); return; } + this.mozKeyPress(e); + //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste // this.cleanUpPaste.defer(100, this); // return; @@ -27950,13 +29631,13 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { getSelection : function() { this.assignDocWin(); - return Roo.isIE ? this.doc.selection : this.win.getSelection(); + return Roo.lib.Selection.wrap(Roo.isIE ? this.doc.selection : this.win.getSelection(), this.doc); }, /** * Select a dom node * @param {DomElement} node the node to select */ - selectNode : function(node) + selectNode : function(node, collapse) { var nodeRange = node.ownerDocument.createRange(); try { @@ -27964,7 +29645,10 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { } catch (e) { nodeRange.selectNodeContents(node); } - //nodeRange.collapse(true); + if (collapse === true) { + nodeRange.collapse(true); + } + // var s = this.win.getSelection(); s.removeAllRanges(); s.addRange(nodeRange); @@ -27976,8 +29660,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { // should we cache this!!!! - - + var range = this.createRange(this.getSelection()).cloneRange(); @@ -28041,6 +29724,8 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { return nodes[0]; }, + + createRange: function(sel) { // this has strange effects when using with @@ -28352,6 +30037,16 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { }, + + updateLanguage : function() + { + if (!this.iframe || !this.iframe.contentDocument) { + return; + } + Roo.get(this.iframe.contentDocument.body).attr("lang", this.language); + }, + + removeStylesheets : function() { var _this = this; @@ -28441,7 +30136,8 @@ Roo.HtmlEditorCore.black = [ 'IFRAME', 'LAYER', 'LINK', 'META', 'OBJECT', 'SCRIPT', 'STYLE' ,'TITLE', 'XML', //'FONT' // CLEAN LATER.. - 'COLGROUP', 'COL' // messy tables. + 'COLGROUP', 'COL' // messy tables. + ]; Roo.HtmlEditorCore.clean = [ // ?? needed???