X-Git-Url: http://git.roojs.org/?a=blobdiff_plain;f=roojs-bootstrap-debug.js;h=87455f01732325aeed95ba567d3cca66eb2386ac;hb=refs%2Fheads%2Fwip_leon_T7605_revamp_image_managment_code;hp=8f4f9b513b33cda645198a2aa537e5b8b99d4167;hpb=a4731897f6fa72bebc326910310b0630b2ce26df;p=roojs1 diff --git a/roojs-bootstrap-debug.js b/roojs-bootstrap-debug.js index 8f4f9b513b..87455f0173 100644 --- a/roojs-bootstrap-debug.js +++ b/roojs-bootstrap-debug.js @@ -17,7 +17,10 @@ Roo.bootstrap.version = ( function() { })(); Roo.bootstrap.menu = Roo.bootstrap.menu || {}; Roo.bootstrap.nav = {}; -Roo.bootstrap.form = {};Roo.bootstrap.panel = {};Roo.bootstrap.layout = {};/* +Roo.bootstrap.form = {};Roo.bootstrap.panel = {};Roo.bootstrap.layout = {}; +Roo.htmleditor = {}; +Roo.namespace('Roo.bootstrap.form.HtmlEditorToolbar'); +/* * Based on: * Ext JS Library 1.1.1 * Copyright(c) 2006-2007, Ext JS, LLC. @@ -577,7 +580,7 @@ Roo.extend(Roo.bootstrap.Component, Roo.BoxComponent, { if (!skip_children) { for(var i =0;i < items.length;i++) { // Roo.log(['add child', items[i]]); - nitems.push(cn.addxtype(Roo.apply({}, items[i]))); + nitems.push(cn.addxtype(items[i].xns == false ? items[i] : Roo.apply({}, items[i]))); } } @@ -4545,10 +4548,16 @@ Roo.extend(Roo.bootstrap.Modal, Roo.bootstrap.Component, { : this.el.select('.modal-footer div',true).first(); }, + + closeClick : function() + { + this.hide(); + }, + initEvents : function() { if (this.allow_close) { - this.closeEl.on('click', this.hide, this); + this.closeEl.on('click', this.closeClick, this); } Roo.EventManager.onWindowResize(this.resize, this, true); if (this.editableTitle) { @@ -9157,6 +9166,7 @@ Currently the Table uses multiple headers to try and handle XL / Medium etc... * also adds table-responsive (see bootstrap docs for details) * @cfg {Boolean} loadMask (true|false) default false * @cfg {Boolean} footerShow (true|false) generate tfoot, default true + * @cfg {Boolean} footerRow (true|false) generate tfoot with columns of values, default false * @cfg {Boolean} headerShow (true|false) generate thead, default true * @cfg {Boolean} rowSelection (true|false) default false * @cfg {Boolean} cellSelection (true|false) default false @@ -9341,6 +9351,7 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component, { store : false, loadMask : false, footerShow : true, + footerRow : false, headerShow : true, enableColumnResize: true, disableAutoSize: false, @@ -9416,9 +9427,10 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component, { cfg.cn.push(this.renderBody()); - if(this.footerShow){ + if(this.footerShow || this.footerRow){ cfg.cn.push(this.renderFooter()); } + // where does this come from? //cfg.cls+= ' TableGrid'; } @@ -9979,8 +9991,6 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component, { return footer; }, - - onLoad : function() { // Roo.log('ds onload'); @@ -10027,7 +10037,7 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component, { var tfoot = this.el.select('tfoot', true).first(); - if(this.footerShow && this.auto_hide_footer && this.mainFoot){ + if(this.footerShow && !this.footerRow && this.auto_hide_footer && this.mainFoot){ this.mainFoot.setVisibilityMode(Roo.Element.DISPLAY).hide(); @@ -10037,6 +10047,30 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component, { this.mainFoot.show(); } } + + if(!this.footerShow && this.footerRow) { + + var tr = { + tag : 'tr', + cn : [] + }; + + for(var i = 0, len = cm.getColumnCount(); i < len; i++){ + var footer = typeof(cm.config[i].footer) == "function" ? cm.config[i].footer(ds, cm.config[i]) : cm.config[i].footer; + var td = { + tag: 'td', + cls : ' x-fcol-' + i, + html: footer + }; + + tr.cn.push(td); + + } + + tfoot.dom.innerHTML = ''; + + tfoot.createChild(tr); + } Roo.each(this.el.select('tbody td', true).elements, function(e){ e.on('mouseover', _this.onMouseover, _this); @@ -10304,7 +10338,7 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component, { var id = false; if(typeof(renderer) !== 'undefined'){ - value = renderer(d.data[cm.getDataIndex(i)], false, d); + value = renderer.call(config, d.data[cm.getDataIndex(i)], false, d); } // if object are returned, then they are expected to be Roo.bootstrap.Component instances // and are rendered into the cells after the row is rendered - using the id for the element. @@ -12512,7 +12546,6 @@ Roo.extend(Roo.bootstrap.form.Input, Roo.bootstrap.Component, { getAutoCreate : function() { - var align = (!this.labelAlign) ? this.parentLabelAlign() : this.labelAlign; var id = Roo.id(); @@ -12641,6 +12674,34 @@ Roo.extend(Roo.bootstrap.form.Input, Roo.bootstrap.Component, { inputblock.cn.push(feedback); } }; + + + + cfg = this.getAutoCreateLabel( cfg, inputblock ); + + + + + if (this.parentType === 'Navbar' && this.parent().bar) { + cfg.cls += ' navbar-form'; + } + + if (this.parentType === 'NavGroup' && !(Roo.bootstrap.version == 4 && this.parent().form)) { + // on BS4 we do this only if not form + cfg.cls += ' navbar-form'; + cfg.tag = 'li'; + } + + return cfg; + + }, + /** + * autocreate the label - also used by textara... ?? and others? + */ + getAutoCreateLabel : function( cfg, inputblock ) + { + var align = (!this.labelAlign) ? this.parentLabelAlign() : this.labelAlign; + var indicator = { tag : 'i', cls : 'roo-required-indicator ' + (this.indicatorpos == 'right' ? 'right' : 'left') +'-indicator text-danger fa fa-lg fa-star', @@ -12784,20 +12845,10 @@ Roo.extend(Roo.bootstrap.form.Input, Roo.bootstrap.Component, { }; - - if (this.parentType === 'Navbar' && this.parent().bar) { - cfg.cls += ' navbar-form'; - } - - if (this.parentType === 'NavGroup' && !(Roo.bootstrap.version == 4 && this.parent().form)) { - // on BS4 we do this only if not form - cfg.cls += ' navbar-form'; - cfg.tag = 'li'; - } - return cfg; - }, + + /** * return the real input element. */ @@ -13037,9 +13088,7 @@ Roo.extend(Roo.bootstrap.form.Input, Roo.bootstrap.Component, { * @return {Mixed} value The field value */ getValue : function(){ - var v = this.inputEl().getValue(); - return v; }, /** @@ -13486,74 +13535,10 @@ Roo.extend(Roo.bootstrap.form.TextArea, Roo.bootstrap.form.Input, { } - if (align ==='left' && this.fieldLabel.length) { - cfg.cn = [ - { - tag: 'label', - 'for' : id, - cls : 'control-label', - html : this.fieldLabel - }, - { - cls : "", - cn: [ - inputblock - ] - } - - ]; - - if(this.labelWidth > 12){ - cfg.cn[0].style = "width: " + this.labelWidth + 'px'; - } - - if(this.labelWidth < 13 && this.labelmd == 0){ - this.labelmd = this.labelWidth; - } - - if(this.labellg > 0){ - cfg.cn[0].cls += ' col-lg-' + this.labellg; - cfg.cn[1].cls += ' col-lg-' + (12 - this.labellg); - } - - if(this.labelmd > 0){ - cfg.cn[0].cls += ' col-md-' + this.labelmd; - cfg.cn[1].cls += ' col-md-' + (12 - this.labelmd); - } - - if(this.labelsm > 0){ - cfg.cn[0].cls += ' col-sm-' + this.labelsm; - cfg.cn[1].cls += ' col-sm-' + (12 - this.labelsm); - } - - if(this.labelxs > 0){ - cfg.cn[0].cls += ' col-xs-' + this.labelxs; - cfg.cn[1].cls += ' col-xs-' + (12 - this.labelxs); - } - - } else if ( this.fieldLabel.length) { - cfg.cn = [ - - { - tag: 'label', - //cls : 'input-group-addon', - html : this.fieldLabel - - }, - - inputblock - - ]; - - } else { - - cfg.cn = [ - - inputblock + + cfg = this.getAutoCreateLabel( cfg, inputblock ); - ]; - - } + if (this.disabled) { input.disabled=true; @@ -13581,11 +13566,11 @@ Roo.extend(Roo.bootstrap.form.TextArea, Roo.bootstrap.form.Input, { } var label = this.el.select('label', true).first(); - var icon = this.el.select('i.fa-star', true).first(); + //var icon = this.el.select('i.fa-star', true).first(); - if(label && icon){ - icon.remove(); - } + //if(label && icon){ + // icon.remove(); + //} this.el.removeClass( this.validClass); this.inputEl().removeClass('is-invalid'); @@ -13627,9 +13612,9 @@ Roo.extend(Roo.bootstrap.form.TextArea, Roo.bootstrap.form.Input, { var label = this.el.select('label', true).first(); var icon = this.el.select('i.fa-star', true).first(); - if(label && icon){ - icon.remove(); - } + //if(label && icon){ + // icon.remove(); + //} if (Roo.bootstrap.version == 3) { this.el.addClass(this.validClass); } else { @@ -13670,21 +13655,22 @@ Roo.extend(Roo.bootstrap.form.TextArea, Roo.bootstrap.form.Input, { this.el.select('.form-control-feedback', true).first().removeClass([this.invalidFeedbackClass, this.validFeedbackClass]); } - if(this.disabled || this.allowBlank){ + if(this.disabled){ return; } var label = this.el.select('label', true).first(); - var icon = this.el.select('i.fa-star', true).first(); + //var icon = this.el.select('i.fa-star', true).first(); - if(!this.getValue().length && label && !icon){ - this.el.createChild({ + //if(!this.getValue().length && label && !icon){ + /* this.el.createChild({ tag : 'i', cls : 'text-danger fa fa-lg fa-star', tooltip : 'This field is required', style : 'margin-right:5px;' }, label, true); - } + */ + //} if (Roo.bootstrap.version == 3) { this.el.addClass(this.invalidClass); @@ -13969,12 +13955,14 @@ Roo.extend(Roo.bootstrap.form.TriggerField, Roo.bootstrap.form.Input, { cls : 'roo-required-indicator ' + (this.indicatorpos == 'right' ? 'right' : 'left') +'-indicator text-danger fa fa-lg fa-star', tooltip : 'This field is required' }; - if (Roo.bootstrap.version == 4) { + + if (this.allowBlank) { indicator = { tag : 'i', style : 'display:none' }; } + if (align ==='left' && this.fieldLabel.length) { @@ -15186,8 +15174,8 @@ Roo.data.Store = function(config){ * If you return Json { data: [] , success: false, .... } then this will be thrown with the following args * * @param {Proxy} - * @param {Object} return from JsonData.reader() - success, totalRecords, records - * @param {Object} load options + * @param {Object} ret return data from JsonData.reader() - success, totalRecords, records + * @param {Object} opts - load Options * @param {Object} jsonData from your request (normally this contains the Exception) */ loadexception : true @@ -16193,24 +16181,24 @@ Roo.extend(Roo.data.HttpProxy, Roo.data.DataProxy, { // thse are take from connection... /** - * @cfg {String} url (Optional) The default URL to be used for requests to the server. (defaults to undefined) + * @cfg {String} url The default URL to be used for requests to the server. (defaults to undefined) */ /** - * @cfg {Object} extraParams (Optional) An object containing properties which are used as + * @cfg {Object} extraParams An object containing properties which are used as * extra parameters to each request made by this object. (defaults to undefined) */ /** - * @cfg {Object} defaultHeaders (Optional) An object containing request headers which are added + * @cfg {Object} defaultHeaders An object containing request headers which are added * to each request made by this object. (defaults to undefined) */ /** - * @cfg {String} method (Optional) The default HTTP method to be used for requests. (defaults to undefined; if not set but parms are present will use POST, otherwise GET) + * @cfg {String} method (GET|POST) The default HTTP method to be used for requests. (defaults to undefined; if not set but parms are present will use POST, otherwise GET) */ /** - * @cfg {Number} timeout (Optional) The timeout in milliseconds to be used for requests. (defaults to 30000) + * @cfg {Number} timeout The timeout in milliseconds to be used for requests. (defaults to 30000) */ /** - * @cfg {Boolean} autoAbort (Optional) Whether this request should abort any pending requests. (defaults to false) + * @cfg {Boolean} autoAbort Whether this request should abort any pending requests. (defaults to false) * @type Boolean */ @@ -17333,12 +17321,13 @@ Roo.extend(Roo.bootstrap.form.ComboBox, Roo.bootstrap.form.TriggerField, { cls : 'roo-required-indicator ' + (this.indicatorpos == 'right' ? 'right' : 'left') +'-indicator text-danger fa fa-lg fa-star', tooltip : 'This field is required' }; - if (Roo.bootstrap.version == 4) { + + if (this.allowBlank) { indicator = { tag : 'i', style : 'display:none' }; - } + } if (align ==='left' && this.fieldLabel.length) { cfg.cls += ' roo-form-group-label-left' + (Roo.bootstrap.version == 4 ? ' row' : ''); @@ -23948,6 +23937,7 @@ Roo.apply(Roo.bootstrap.form.DateField, { * @class Roo.bootstrap.form.TimeField * @extends Roo.bootstrap.form.Input * Bootstrap DateField class + * @cfg {Number} minuteStep the minutes is always the multiple of a fixed number, default 1 * * * @constructor @@ -23990,6 +23980,7 @@ Roo.extend(Roo.bootstrap.form.TimeField, Roo.bootstrap.form.Input, { * valid according to {@link Date#parseDate} (defaults to 'H:i'). */ format : "H:i", + minuteStep : 1, getAutoCreate : function() { @@ -24217,7 +24208,11 @@ Roo.extend(Roo.bootstrap.form.TimeField, Roo.bootstrap.form.Input, { update: function() { - + // default minute is a multiple of minuteStep + if(typeof(this.time) === 'undefined') { + this.time = new Date(); + this.time = this.time.add(Date.MINUTE, Math.round(parseInt(this.time.format('i')) / this.minuteStep) * this.minuteStep - parseInt(this.time.format('i'))); + } this.time = (typeof(this.time) === 'undefined') ? new Date() : this.time; this.fill(); @@ -24370,14 +24365,16 @@ Roo.extend(Roo.bootstrap.form.TimeField, Roo.bootstrap.form.Input, { onIncrementMinutes: function() { Roo.log('onIncrementMinutes'); - this.time = this.time.add(Date.MINUTE, 1); + var minutesToAdd = Math.round((parseInt(this.time.format('i')) + this.minuteStep) / this.minuteStep) * this.minuteStep - parseInt(this.time.format('i')); + this.time = this.time.add(Date.MINUTE, minutesToAdd); this.update(); }, onDecrementMinutes: function() { Roo.log('onDecrementMinutes'); - this.time = this.time.add(Date.MINUTE, -1); + var minutesToSubtract = parseInt(this.time.format('i')) - Math.round((parseInt(this.time.format('i')) - this.minuteStep) / this.minuteStep) * this.minuteStep; + this.time = this.time.add(Date.MINUTE, -1 * minutesToSubtract); this.update(); }, @@ -26005,52 +26002,544 @@ Roo.extend(Roo.bootstrap.form.SecurePass, Roo.bootstrap.form.Input, { return this.IsLongEnough(pwd, 6) || !this.IsLongEnough(pwd, 0); } +});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.htmleditor = {}; +Roo.rtf.Ctrl = function(opts) +{ + this.value = opts.value; + this.param = opts.param; +}; /** - * @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 + * + * + * 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.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 = { +Roo.rtf.Parser = function(text) { + //super({objectMode: true}) + this.text = ''; + this.parserState = this.parseText; - node: false, + // these are for interpeter... + this.doc = {}; + ///this.parserState = this.parseTop + this.groupStack = []; + this.hexStore = []; + this.doc = false; - tag: false, - - // overrride to do replace comments. - replaceComment : false, + this.groups = []; // where we put the return. - // overrride to do replace or do stuff with tags.. - replaceTag : false, + for (var ii = 0; ii < text.length; ++ii) { + ++this.cpos; + + if (text[ii] === '\n') { + ++this.row; + this.col = 1; + } else { + ++this.col; + } + this.parserState(text[ii]); + } - walk : function(dom) + + +}; +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) { - 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 + 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 + return; // we really don't care about stray text... + } + this.group.addContent(new Roo.rtf.Span(cmd)); + }, + cmdcontrolword : function (cmd) + { + this.flushHexStore(); + if (!this.group.type) { + this.group.type = cmd.value; + return; + } + this.group.addContent(new Roo.rtf.Ctrl(cmd)); + // we actually don't care about ctrl words... + return ; + /* + var method = 'ctrl$' + cmd.value.replace(/-(.)/g, (_, char) => char.toUpperCase()) + if (this[method]) { + this[method](cmd.param) + } else { + if (!this.group.get('ignorable')) process.emit('debug', method, cmd.param) + } + */ + }, + cmdhexchar : function(cmd) { + this.hexStore.push(cmd); + }, + cmderror : function(cmd) { + throw cmd.value; + }, + + /* + _flush (done) { + if (this.text !== '\u0000') this.emitText() + done() + } + */ + + + parseText : function(c) + { + if (c === '\\') { + this.parserState = this.parseEscapes; + } else if (c === '{') { + this.emitStartGroup(); + } else if (c === '}') { + this.emitEndGroup(); + } else if (c === '\x0A' || c === '\x0D') { + // cr/lf are noise chars + } else { + this.text += c; + } + }, + + parseEscapes: function (c) + { + if (c === '\\' || c === '{' || c === '}') { + this.text += c; + this.parserState = this.parseText; + } else { + this.parserState = this.parseControlSymbol; + this.parseControlSymbol(c); + } + }, + parseControlSymbol: function(c) + { + if (c === '~') { + this.text += '\u00a0'; // nbsp + this.parserState = this.parseText + } else if (c === '-') { + this.text += '\u00ad'; // soft hyphen + } else if (c === '_') { + this.text += '\u2011'; // non-breaking hyphen + } else if (c === '*') { + this.emitIgnorable(); + this.parserState = this.parseText; + } else if (c === "'") { + this.parserState = this.parseHexChar; + } else if (c === '|') { // formula cacter + this.emitFormula(); + this.parserState = this.parseText; + } else if (c === ':') { // subentry in an index entry + this.emitIndexSubEntry(); + this.parserState = this.parseText; + } else if (c === '\x0a') { + this.emitEndParagraph(); + this.parserState = this.parseText; + } else if (c === '\x0d') { + this.emitEndParagraph(); + this.parserState = this.parseText; + } else { + this.parserState = this.parseControlWord; + this.parseControlWord(c); + } + }, + parseHexChar: function (c) + { + if (/^[A-Fa-f0-9]$/.test(c)) { + this.hexChar += c; + if (this.hexChar.length >= 2) { + this.emitHexChar(); + this.parserState = this.parseText; + } + return; + } + this.emitError("Invalid character \"" + c + "\" in hex literal."); + this.parserState = this.parseText; + + }, + parseControlWord : function(c) + { + if (c === ' ') { + this.emitControlWord(); + this.parserState = this.parseText; + } else if (/^[-\d]$/.test(c)) { + this.parserState = this.parseControlWordParam; + this.controlWordParam += c; + } else if (/^[A-Za-z]$/.test(c)) { + this.controlWord += c; + } else { + this.emitControlWord(); + this.parserState = this.parseText; + this.parseText(c); + } + }, + parseControlWordParam : function (c) { + if (/^\d$/.test(c)) { + this.controlWordParam += c; + } else if (c === ' ') { + this.emitControlWord(); + this.parserState = this.parseText; + } else { + this.emitControlWord(); + this.parserState = this.parseText; + this.parseText(c); + } + }, + + + + + emitText : function () { + if (this.text === '') { + return; + } + this.push({ + type: 'text', + value: this.text, + pos: this.cpos, + row: this.row, + col: this.col + }); + this.text = '' + }, + emitControlWord : function () + { + this.emitText(); + if (this.controlWord === '') { + // do we want to track this - it seems just to cause problems. + //this.emitError('empty control word'); + } else { + this.push({ + type: 'controlword', + value: this.controlWord, + param: this.controlWordParam !== '' && Number(this.controlWordParam), + pos: this.cpos, + row: this.row, + col: this.col + }); + } + this.controlWord = ''; + this.controlWordParam = ''; + }, + emitStartGroup : function () + { + this.emitText(); + this.push({ + type: 'groupstart', + pos: this.cpos, + row: this.row, + col: this.col + }); + }, + emitEndGroup : function () + { + this.emitText(); + this.push({ + type: 'groupend', + pos: this.cpos, + row: this.row, + col: this.col + }); + }, + emitIgnorable : function () + { + this.emitText(); + this.push({ + type: 'ignorable', + pos: this.cpos, + row: this.row, + col: this.col + }); + }, + emitHexChar : function () + { + this.emitText(); + this.push({ + type: 'hexchar', + value: this.hexChar, + pos: this.cpos, + row: this.row, + col: this.col + }); + this.hexChar = '' + }, + emitError : function (message) + { + this.emitText(); + this.push({ + type: 'error', + value: message, + row: this.row, + col: this.col, + char: this.cpos //, + //stack: new Error().stack + }); + }, + emitEndParagraph : function () { + this.emitText(); + this.push({ + type: 'endparagraph', + pos: this.cpos, + row: this.row, + col: this.col + }); + } + +} ; +/** + * @class Roo.htmleditor.Filter + * Base Class for filtering htmleditor stuff. - do not use this directly - extend it. + * @cfg {DomElement} node The node to iterate and filter + * @cfg {boolean|String|Array} tag Tags to replace + * @constructor + * Create a new Filter. + * @param {Object} config Configuration options + */ + + + +Roo.htmleditor.Filter = function(cfg) { + Roo.apply(this.cfg); + // this does not actually call walk as it's really just a abstract class +} + + +Roo.htmleditor.Filter.prototype = { + + node: false, + + tag: false, + + // overrride to do replace comments. + replaceComment : false, + + // overrride to do replace or do stuff with tags.. + replaceTag : false, + + walk : function(dom) + { + Roo.each( Array.from(dom.childNodes), function( e ) { + switch(true) { + + case e.nodeType == 8 && this.replaceComment !== false: // comment + this.replaceComment(e); + return; + + case e.nodeType != 1: //not a node. + return; + + case this.tag === true: // everything case e.tagName.indexOf(":") > -1 && typeof(this.tag) == 'object' && this.tag.indexOf(":") > -1: case e.tagName.indexOf(":") > -1 && typeof(this.tag) == 'string' && this.tag == ":": case typeof(this.tag) == 'object' && this.tag.indexOf(e.tagName) > -1: // array and it matches. @@ -26510,7 +26999,7 @@ Roo.htmleditor.FilterWord = function(cfg) this.replaceAname(cfg.node); // this is disabled as the removal is done by other filters; // this.walk(cfg.node); - + this.replaceImageTable(cfg.node); } @@ -26718,7 +27207,8 @@ Roo.extend(Roo.htmleditor.FilterWord, Roo.htmleditor.Filter, parent = p.parentNode, doc = parent.ownerDocument, items = []; - + + //Roo.log("Parsing: " + p.innerText) ; var listtype = 'ul'; while (ns) { if (ns.nodeType != 1) { @@ -26726,22 +27216,38 @@ Roo.extend(Roo.htmleditor.FilterWord, Roo.htmleditor.Filter, continue; } if (!ns.className.match(/(MsoListParagraph|ql-indent-1)/i)) { + //Roo.log("Missing para r q1indent - got:" + ns.className); break; } var spans = ns.getElementsByTagName('span'); + if (ns.hasAttribute('style') && ns.getAttribute('style').match(/mso-list/)) { items.push(ns); ns = ns.nextSibling; has_list = true; - if (spans.length && spans[0].hasAttribute('style')) { - var style = this.styleToObject(spans[0]); - if (typeof(style['font-family']) != 'undefined' && !style['font-family'].match(/Symbol/)) { - listtype = 'ol'; + if (!spans.length) { + continue; + } + var ff = ''; + var se = spans[0]; + for (var i = 0; i < spans.length;i++) { + se = spans[i]; + if (se.hasAttribute('style') && se.hasAttribute('style') && se.style.fontFamily != '') { + ff = se.style.fontFamily; + break; } } + + + //Roo.log("got font family: " + ff); + if (typeof(ff) != 'undefined' && !ff.match(/(Symbol|Wingdings)/) && "·o".indexOf(se.innerText.trim()) < 0) { + listtype = 'ol'; + } continue; } + //Roo.log("no mso-list?"); + var spans = ns.getElementsByTagName('span'); if (!spans.length) { break; @@ -26852,9 +27358,63 @@ Roo.extend(Roo.htmleditor.FilterWord, Roo.htmleditor.Filter, - } - + }, + replaceImageTable : function(doc) + { + /* + + + + + + + + +
+ */ + var imgs = Array.from(doc.getElementsByTagName('img')); + Roo.each(imgs, function(img) { + var td = img.parentNode; + if (td.nodeName != 'TD') { + return; + } + var tr = td.parentNode; + if (tr.nodeName != 'TR') { + return; + } + var tbody = tr.parentNode; + if (tbody.nodeName != 'TBODY') { + return; + } + var table = tbody.parentNode; + if (table.nodeName != 'TABLE') { + return; + } + // first row.. + + if (table.getElementsByTagName('tr').length != 2) { + return; + } + if (table.getElementsByTagName('td').length != 3) { + return; + } + if (table.innerText.trim() != '') { + return; + } + var p = table.parentNode; + img.parentNode.removeChild(img); + p.insertBefore(img, table); + p.removeChild(table); + + + + }); + + + } }); /** @@ -28772,7 +29332,7 @@ Roo.extend(Roo.htmleditor.BlockFigure, Roo.htmleditor.Block, { } }, - + { xtype : 'Button', text: 'Hide Caption', @@ -28867,7 +29427,8 @@ Roo.extend(Roo.htmleditor.BlockFigure, Roo.htmleditor.Block, { var ret = { tag: 'figure', 'data-block' : 'Figure', - 'data-width' : this.width, + 'data-width' : this.width, + 'data-caption' : this.caption, contenteditable : 'false', style : { @@ -28940,6 +29501,8 @@ Roo.extend(Roo.htmleditor.BlockFigure, Roo.htmleditor.Block, { this.image_src = this.getVal(node, 'img', 'src'); this.align = this.getVal(node, 'figure', 'align'); + + /// not really used - as hidden captions do not store the content here.. var figcaption = this.getVal(node, 'figcaption', false); if (figcaption !== '') { this.caption = this.getVal(figcaption, 'i', 'html'); @@ -28947,6 +29510,10 @@ Roo.extend(Roo.htmleditor.BlockFigure, Roo.htmleditor.Block, { this.caption_display = this.getVal(node, 'figcaption', 'data-display'); + var dc = this.getVal(node, true, 'data-caption'); + if (dc && dc.length) { + this.caption = dc; + } //this.text_align = this.getVal(node, 'figcaption', 'style','text-align'); this.width = this.getVal(node, true, 'data-width'); //this.margin = this.getVal(node, 'figure', 'style', 'margin'); @@ -30287,7 +30854,7 @@ Roo.HtmlEditorCore = function(config){ * @param {Roo.HtmlEditorCore} this */ editorevent: true - + }); @@ -30311,10 +30878,9 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { owner : false, /** - * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a - * Roo.resizable. + * @cfg {String} css styling for resizing. (used on bootstrap only) */ - resizable : false, + resize : false, /** * @cfg {Number} height (in pixels) */ @@ -30447,17 +31013,19 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { this.frameId = Roo.id(); - - - var iframe = this.owner.wrap.createChild({ + var ifcfg = { tag: 'iframe', cls: 'form-control', // bootstrap.. id: this.frameId, name: this.frameId, frameBorder : 'no', 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false" - }, this.el - ); + }; + if (this.resize) { + ifcfg.style = { resize : this.resize }; + } + + var iframe = this.owner.wrap.createChild(ifcfg, this.el); this.iframe = iframe.dom; @@ -30609,6 +31177,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { 'rowspan', 'data-display', 'data-width', + 'data-caption', 'start' , 'style', // youtube embed. @@ -30815,14 +31384,39 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { var cd = (e.browserEvent.clipboardData || window.clipboardData); // check what type of paste - if it's an image, then handle it differently. - if (cd.files && cd.files.length > 0) { - // pasting images? + if (cd.files && cd.files.length > 0 && cd.types.indexOf('text/html') < 0) { + // pasting images? var urlAPI = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL); - - var url = urlAPI.createObjectURL( cd.files[0]); - this.insertAtCursor(''); + + var r = new FileReader(); + var t = this; + r.addEventListener('load',function() + { + + var d = (new DOMParser().parseFromString('', 'text/html')).body; + // is insert asycn? + if (t.enableBlocks) { + + Array.from(d.getElementsByTagName('img')).forEach(function(img) { + if (img.closest('figure')) { // assume!! that it's aready + return; + } + var fig = new Roo.htmleditor.BlockFigure({ + image_src : img.src + }); + fig.updateElement(img); // replace it.. + + }); + } + t.insertAtCursor(d.innerHTML.replace(/ /g,' ')); + t.owner.fireEvent('paste', this); + }); + r.readAsDataURL(cd.files[0]); + + e.preventDefault(); + return false; } if (cd.types.indexOf('text/html') < 0 ) { @@ -30915,6 +31509,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { e.preventDefault(); + this.owner.fireEvent('paste', this); return false; // default behaveiour should be our local cleanup paste? (optional?) // for simple editor - we want to hammer the paste and get rid of everything... - so over-rideable.. @@ -30991,6 +31586,8 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { if (e && (e.ctrlKey || e.metaKey) && e.keyCode === 90) { return; // we do not handle this.. (undo manager does..) } + // clicking a 'block'? + // in theory this detects if the last element is not a br, then we try and do that. // its so clicking in space at bottom triggers adding a br and moving the cursor. if (e && @@ -31093,6 +31690,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { break; case 'bold': case 'italic': + case 'underline': // if there is no selection, then we insert, and set the curson inside it.. this.execCmd('styleWithCSS', false); break; @@ -31877,31 +32475,26 @@ Roo.HtmlEditorCore.cblack= [ */ Roo.bootstrap.form.HtmlEditor = function(config){ - Roo.bootstrap.form.HtmlEditor.superclass.constructor.call(this, config); - if (!this.toolbars) { - this.toolbars = []; - } - - this.editorcore = new Roo.HtmlEditorCore(Roo.apply({ owner : this} , config)); + this.addEvents({ /** * @event initialize * Fires when the editor is fully initialized (including the iframe) - * @param {HtmlEditor} this + * @param {Roo.bootstrap.form.HtmlEditor} this */ initialize: true, /** * @event activate * Fires when the editor is first receives the focus. Any insertion must wait * until after this event. - * @param {HtmlEditor} this + * @param {Roo.bootstrap.form.HtmlEditor} this */ activate: true, /** * @event beforesync * Fires before the textarea is updated with content from the editor iframe. Return false * to cancel the sync. - * @param {HtmlEditor} this + * @param {Roo.bootstrap.form.HtmlEditor} this * @param {String} html */ beforesync: true, @@ -31909,56 +32502,96 @@ Roo.bootstrap.form.HtmlEditor = function(config){ * @event beforepush * Fires before the iframe editor is updated with content from the textarea. Return false * to cancel the push. - * @param {HtmlEditor} this + * @param {Roo.bootstrap.form.HtmlEditor} this * @param {String} html */ beforepush: true, /** * @event sync * Fires when the textarea is updated with content from the editor iframe. - * @param {HtmlEditor} this + * @param {Roo.bootstrap.form.HtmlEditor} this * @param {String} html */ sync: true, /** * @event push * Fires when the iframe editor is updated with content from the textarea. - * @param {HtmlEditor} this + * @param {Roo.bootstrap.form.HtmlEditor} this * @param {String} html */ push: true, /** * @event editmodechange * Fires when the editor switches edit modes - * @param {HtmlEditor} this + * @param {Roo.bootstrap.form.HtmlEditor} this * @param {Boolean} sourceEdit True if source edit, false if standard editing. */ editmodechange: true, /** * @event editorevent * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks. - * @param {HtmlEditor} this + * @param {Roo.bootstrap.form.HtmlEditor} this */ editorevent: true, /** * @event firstfocus * Fires when on first focus - needed by toolbars.. - * @param {HtmlEditor} this + * @param {Roo.bootstrap.form.HtmlEditor} this */ firstfocus: true, /** * @event autosave * Auto save the htmlEditor value as a file into Events - * @param {HtmlEditor} this + * @param {Roo.bootstrap.form.HtmlEditor} this */ autosave: true, /** * @event savedpreview * preview the saved version of htmlEditor - * @param {HtmlEditor} this + * @param {Roo.bootstrap.form.HtmlEditor} this */ - savedpreview: true - }); + savedpreview: true, + /** + * @event stylesheetsclick + * Fires when press the Sytlesheets button + * @param {Roo.HtmlEditorCore} this + */ + stylesheetsclick: true, + /** + * @event paste + * Fires when press user pastes into the editor + * @param {Roo.HtmlEditorCore} this + */ + paste: true, + /** + * @event imageadd + * Fires when on any editor when an image is added (excluding paste) + * @param {Roo.bootstrap.form.HtmlEditor} this + */ + imageadd: true , + /** + * @event imageupdated + * Fires when on any editor when an image is changed (excluding paste) + * @param {Roo.bootstrap.form.HtmlEditor} this + * @param {HTMLElement} img could also be a figure if blocks are enabled + */ + imageupdate: true , + /** + * @event imagedelete + * Fires when on any editor when an image is deleted + * @param {Roo.bootstrap.form.HtmlEditor} this + * @param {HTMLElement} img could also be a figure if blocks are enabled + * @param {HTMLElement} oldSrc source of image being replaced + */ + imagedelete: true + }); + Roo.bootstrap.form.HtmlEditor.superclass.constructor.call(this, config); + if (!this.toolbars) { + this.toolbars = []; + } + + this.editorcore = new Roo.HtmlEditorCore(Roo.apply({ owner : this} , config)); + }; @@ -31966,9 +32599,9 @@ Roo.extend(Roo.bootstrap.form.HtmlEditor, Roo.bootstrap.form.TextArea, { /** - * @cfg {Array} toolbars Array of toolbars. - defaults to just the Standard one + * @cfg {Array|boolean} toolbars Array of toolbars, or names of toolbars. - true for standard, and false for none. */ - toolbars : false, + toolbars : true, /** * @cfg {Array} buttons Array of toolbar's buttons. - defaults to empty @@ -31976,10 +32609,9 @@ Roo.extend(Roo.bootstrap.form.HtmlEditor, Roo.bootstrap.form.TextArea, { btns : [], /** - * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a - * Roo.resizable. + * @cfg {String} resize (none|both|horizontal|vertical) - css resize of element */ - resizable : false, + resize : false, /** * @cfg {Number} height (in pixels) */ @@ -32022,26 +32654,35 @@ Roo.extend(Roo.bootstrap.form.HtmlEditor, Roo.bootstrap.form.TextArea, { * add custom toolbar buttons. * @param {HtmlEditor} editor */ - createToolbar : function(){ - Roo.log('renewing'); - Roo.log("create toolbars"); + createToolbar : function() + { + //Roo.log('renewing'); + //Roo.log("create toolbars"); + if (this.toolbars === false) { + return; + } + if (this.toolbars === true) { + this.toolbars = [ 'Standard' ]; + } - this.toolbars = [ new Roo.bootstrap.form.HtmlEditorToolbarStandard({editor: this} ) ]; - this.toolbars[0].render(this.toolbarContainer()); + var ar = Array.from(this.toolbars); + this.toolbars = []; + ar.forEach(function(t,i) { + if (typeof(t) == 'string') { + t = { + xtype : t + }; + } + if (typeof(t) == 'object' && typeof(t.xtype) == 'string') { + t.editor = this; + t.xns = t.xns || Roo.bootstrap.form.HtmlEditorToolbar; + t = Roo.factory(t); + } + this.toolbars[i] = t; + this.toolbars[i].render(this.toolbarContainer()); + }, this); - return; -// if (!editor.toolbars || !editor.toolbars.length) { -// editor.toolbars = [ new Roo.bootstrap.form.HtmlEditorToolbarStandard() ]; // can be empty? -// } -// -// for (var i =0 ; i < editor.toolbars.length;i++) { -// editor.toolbars[i] = Roo.factory( -// typeof(editor.toolbars[i]) == 'string' ? -// { xtype: editor.toolbars[i]} : editor.toolbars[i], -// Roo.bootstrap.form.HtmlEditor); -// editor.toolbars[i].init(editor); -// } }, @@ -32058,33 +32699,11 @@ Roo.extend(Roo.bootstrap.form.HtmlEditor, Roo.bootstrap.form.TextArea, { this.editorcore.onRender(ct, position); - if (this.resizable) { - this.resizeEl = new Roo.Resizable(this.wrap, { - pinned : true, - wrap: true, - dynamic : true, - minHeight : this.height, - height: this.height, - handles : this.resizable, - width: this.width, - listeners : { - resize : function(r, w, h) { - _t.onResize(w,h); // -something - } - } - }); - - } + this.createToolbar(this); - if(!this.width && this.resizable){ - this.setSize(this.wrap.getSize()); - } - if (this.resizeEl) { - this.resizeEl.resizeTo.defer(100, this.resizeEl,[ this.width,this.height ] ); - // should trigger onReize.. - } + }, @@ -32155,9 +32774,9 @@ Roo.extend(Roo.bootstrap.form.HtmlEditor, Roo.bootstrap.form.TextArea, { //this.deferFocus(); } - if(this.resizable){ - this.setSize(this.wrap.getSize()); - } + //if(this.resizable){ + // this.setSize(this.wrap.getSize()); + //} this.fireEvent('editmodechange', this, this.editorcore.sourceEditMode); }, @@ -32294,9 +32913,8 @@ Roo.extend(Roo.bootstrap.form.HtmlEditor, Roo.bootstrap.form.TextArea, { -Roo.namespace('Roo.bootstrap.form.HtmlEditor'); /** - * @class Roo.bootstrap.form.HtmlEditorToolbarStandard + * @class Roo.bootstrap.form.HtmlEditorToolbar.Standard * @parent Roo.bootstrap.form.HtmlEditor * @extends Roo.bootstrap.nav.Simplebar * Basic Toolbar @@ -32307,7 +32925,7 @@ Roo.namespace('Roo.bootstrap.form.HtmlEditor'); new Roo.bootstrap.form.HtmlEditor({ .... toolbars : [ - new Roo.bootstrap.form.HtmlEditorToolbarStandard({ + new Roo.bootstrap.form.HtmlEditorToolbar.Standard({ disable : { fonts: 1 , format: 1, ..., ... , ...], btns : [ .... ] }) @@ -32322,7 +32940,7 @@ Roo.namespace('Roo.bootstrap.form.HtmlEditor'); * .x-html-editor-tb .x-edit-none .x-btn-text { background: none; } */ -Roo.bootstrap.form.HtmlEditorToolbarStandard = function(config) +Roo.bootstrap.form.HtmlEditorToolbar.Standard = function(config) { Roo.apply(this, config); @@ -32334,17 +32952,17 @@ Roo.bootstrap.form.HtmlEditorToolbarStandard = function(config) colors : true, specialElements : true }); - Roo.bootstrap.form.HtmlEditorToolbarStandard.superclass.constructor.call(this, config); + Roo.bootstrap.form.HtmlEditorToolbar.Standard.superclass.constructor.call(this, config); this.editor = config.editor; this.editorcore = config.editor.editorcore; - this.buttons = new Roo.util.MixedCollection(false, function(o) { return o.cmd; }); + this.buttons = new Roo.util.MixedCollection(false, function(o) { return o.btnid; }); //Roo.form.HtmlEditorToolbar1.superclass.constructor.call(this, editor.wrap.dom.firstChild, [], config); // dont call parent... till later. } -Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simplebar, { +Roo.extend(Roo.bootstrap.form.HtmlEditorToolbar.Standard, Roo.bootstrap.nav.Simplebar, { bar : true, @@ -32360,11 +32978,14 @@ Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simpl 'div','span' ], + + deleteBtn: false, + onRender : function(ct, position) { // Roo.log("Call onRender: " + this.xtype); - Roo.bootstrap.form.HtmlEditorToolbarStandard.superclass.onRender.call(this, ct, position); + Roo.bootstrap.form.HtmlEditorToolbar.Standard.superclass.onRender.call(this, ct, position); Roo.log(this.el); this.el.dom.style.marginBottom = '0'; var _this = this; @@ -32372,7 +32993,7 @@ Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simpl var editor= this.editor; var children = []; - var btn = function(id,cmd , toggle, handler, html){ + var btn = function(id, cmd , toggle, handler, html){ var event = toggle ? 'toggle' : 'click'; @@ -32381,9 +33002,11 @@ Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simpl xtype: 'Button', xns: Roo.bootstrap, //glyphicon : id, + btnid : id, fa: id, - cmd : id || cmd, - enableToggle:toggle !== false, + cls : 'roo-html-editor-btn-' + id, + cmd : cmd, // why id || cmd + enableToggle: toggle !== false, html : html || '', pressed : toggle ? false : null, listeners : {} @@ -32402,6 +33025,7 @@ Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simpl size : 'sm', xns: Roo.bootstrap, fa : 'font', + cls : 'roo-html-editor-font-chooser', //html : 'submit' menu : { xtype: 'Menu', @@ -32427,19 +33051,19 @@ Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simpl }); children.push(style); - btn('bold',false,true); - btn('italic',false,true); - btn('align-left', 'justifyleft',true); + btn('bold', 'bold',true); + btn('italic', 'italic',true); + btn('underline', 'underline',true); + btn('align-left', 'justifyleft',true); btn('align-center', 'justifycenter',true); btn('align-right' , 'justifyright',true); - btn('link', false, false, function(btn) { - //Roo.log("create link?"); - var url = prompt(this.createLinkText, this.defaultLinkValue); - if(url && url != 'http:/'+'/'){ - this.editorcore.relayCmd('createlink', url); - } - }), + btn('link', false, true, this.onLinkClick); + + + btn('image', false, true, this.onImageClick); btn('list','insertunorderedlist',true); + btn('list-ol','insertorderedlist',true); + btn('pencil', false,true, function(btn){ Roo.log(this); this.toggleSourceEdit(btn.pressed); @@ -32451,58 +33075,178 @@ Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simpl } } - /* - var cog = { - xtype: 'Button', - size : 'sm', - xns: Roo.bootstrap, - glyphicon : 'cog', - //html : 'submit' - menu : { - xtype: 'Menu', - xns: Roo.bootstrap, - items: [] - } - }; - - cog.menu.items.push({ - xtype :'MenuItem', - xns: Roo.bootstrap, - html : Clean styles, - tagname : f, - listeners : { - click : function() - { - editorcore.insertTag(this.tagname); - editor.focus(); - } - } - - }); - */ - this.xtype = 'NavSimplebar'; + this.xtype = 'NavSimplebar'; // why? for(var i=0;i< children.length;i++) { this.buttons.add(this.addxtypeChild(children[i])); } - + this.buildToolbarDelete(); + editor.on('editorevent', this.updateToolbar, this); }, + + buildToolbarDelete : function() + { + + /* this.addxtypeChild({ + xtype : 'Element', + xns : Roo.bootstrap, + cls : 'roo-htmleditor-fill' + }); + */ + this.deleteBtn = this.addxtypeChild({ + size : 'sm', + xtype: 'Button', + xns: Roo.bootstrap, + fa: 'trash', + listeners : { + click : this.onDelete.createDelegate(this) + } + }); + this.deleteBtn.hide(); + + }, + + onImageClick : function() + { + if (this.input) { + this.input.un('change', this.onFileSelected, this); + } + this.input = Roo.get(document.body).createChild({ + tag: 'input', + type : 'file', + style : 'display:none', + multiple: 'multiple' + }); + this.input.on('change', this.onFileSelected, this); + this.input.dom.click(); + }, + + onFileSelected : function(e) + { + e.preventDefault(); + + if(typeof(this.input.dom.files) == 'undefined' || !this.input.dom.files.length){ + return; + } + + + this.addFiles(Array.prototype.slice.call(this.input.dom.files), false); + }, + + addFiles : function(far, fire_add) { + + + var editor = this.editorcore; + + if (!far.length) { + if (fire_add) { + this.editor.syncValue(); + editor.owner.fireEvent('editorevent', editor.owner, false); + editor.owner.fireEvent('imageadd', editor.owner, false); + } + return; + } + + var f = far.pop(); + + if (!f.type.match(/^image/)) { + this.addFiles(far, fire_add); + return; + } + + var sn = this.selectedNode; + + var bl = sn && this.editorcore.enableBlocks ? Roo.htmleditor.Block.factory(sn) : false; + + + var reader = new FileReader(); + reader.addEventListener('load', (function() { + if (bl) { + var oldSrc = bl.image_src; + bl.image_src = reader.result; + //bl.caption = f.name; + bl.updateElement(sn); + this.editor.syncValue(); + editor.owner.fireEvent('editorevent', editor.owner, false); + editor.owner.fireEvent('imageupdate', editor.owner, sn, oldSrc); + // we only do the first file!! and replace. + return; + } + if (this.editorcore.enableBlocks) { + var fig = new Roo.htmleditor.BlockFigure({ + image_src : reader.result, + caption : '', + caption_display : 'none' //default to hide captions.. + }); + editor.insertAtCursor(fig.toHTML()); + this.addFiles(far, true); + return; + } + // just a standard img.. + if (sn && sn.tagName.toUpperCase() == 'IMG') { + var oldSrc = sn.src; + sn.src = reader.result; + this.editor.syncValue(); + editor.owner.fireEvent('editorevent', editor.owner, false); + editor.owner.fireEvent('imageupdate', editor.owner, sn, oldSrc); + return; + } + editor.insertAtCursor(''); + this.addFiles(far, true); + + }).createDelegate(this)); + reader.readAsDataURL(f); + + + }, + + onBtnClick : function(id) { this.editorcore.relayCmd(id); this.editorcore.focus(); }, + onLinkClick : function(btn) { + var url = this.selectedNode && this.selectedNode.tagName.toUpperCase() == 'A' ? + this.selectedNode.getAttribute('href') : ''; + + Roo.bootstrap.MessageBox.show({ + title : "Add / Edit Link URL", + msg : "Enter the URL for the link", + buttons: Roo.bootstrap.MessageBox.OKCANCEL, + minWidth: 250, + scope : this, + prompt:true, + multiline: false, + modal : true, + value : url, + fn: function(pressed, newurl) { + if (pressed != 'ok') { + this.editorcore.focus(); + return; + } + if (url != '') { + this.selectedNode.setAttribute('href', newurl); + return; + } + if(newurl && newurl .match(/http(s):\/\/.+/)) { + this.editorcore.relayCmd('createlink', newurl); + } + this.editorcore.focus(); + } + }); + }, /** * Protected method that will not generally be called directly. It triggers * a toolbar update by reading the markup state of the current selection in the editor. */ - updateToolbar: function(){ + updateToolbar: function(editor ,ev, sel){ if(!this.editorcore.activated){ this.editor.onFirstFocus(); // is this neeed? @@ -32511,39 +33255,89 @@ Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simpl var btns = this.buttons; var doc = this.editorcore.doc; - btns.get('bold').setActive(doc.queryCommandState('bold')); - btns.get('italic').setActive(doc.queryCommandState('italic')); - //btns.get('underline').setActive(doc.queryCommandState('underline')); + var hasToggle = false; + btns.each(function(e) { + if (e.enableToggle && e.cmd) { + hasToggle = hasToggle || (['align-left', 'align-right', 'align-center', 'image' , 'link', 'underline'].indexOf(e.btnid) < 0 && doc.queryCommandState(e.cmd)); + e.setActive(doc.queryCommandState(e.cmd)); + } + }, this); - btns.get('align-left').setActive(doc.queryCommandState('justifyleft')); - btns.get('align-center').setActive(doc.queryCommandState('justifycenter')); - btns.get('align-right').setActive(doc.queryCommandState('justifyright')); - //btns[frameId + '-insertorderedlist').setActive(doc.queryCommandState('insertorderedlist')); - btns.get('list').setActive(doc.queryCommandState('insertunorderedlist')); - /* + if (ev && + (ev.type == 'mouseup' || ev.type == 'click' ) && + ev.target && ev.target.tagName != 'BODY' ) { // && ev.target.tagName == 'IMG') { + // they have click on an image... + // let's see if we can change the selection... + sel = ev.target; + + } var ans = this.editorcore.getAllAncestors(); - if (this.formatCombo) { + if (!sel) { + sel = ans.length ? (ans[0] ? ans[0] : ans[1]) : this.editorcore.doc.body; + sel = sel ? sel : this.editorcore.doc.body; + sel = sel.tagName.length ? sel : this.editorcore.doc.body; + } + + var lastSel = this.selectedNode; + this.selectedNode = sel; + + // ok see if we are editing a block? + + var db = false; + // you are not actually selecting the block. + if (sel && sel.hasAttribute('data-block')) { + db = sel; + } else if (sel && sel.closest('[data-block]')) { + db = sel.closest('[data-block]'); + } + + Array.from(this.editorcore.doc.body.querySelectorAll('.roo-ed-selection')).forEach(function(e) { + e.classList.remove('roo-ed-selection'); + }); + + var block = false; + if (db && this.editorcore.enableBlocks) { + block = Roo.htmleditor.Block.factory(db); - var store = this.formatCombo.store; - this.formatCombo.setValue(""); - for (var i =0; i < ans.length;i++) { - if (ans[i] && store.query('tag',ans[i].tagName.toLowerCase(), false).length) { - // select it.. - this.formatCombo.setValue(ans[i].tagName.toLowerCase()); - break; - } + if (block) { + db.className = (db.classList.length > 0 ? db.className + ' ' : '') + + ' roo-ed-selection'; + sel = this.selectedNode = db; } } + // highlight the 'a'.. + var tn = sel && sel.tagName.toUpperCase() || ''; + if (!block && sel && tn != 'A') { + var asel = sel.closest('A'); + if (asel) { + sel = asel; + } + } + + btns.get('link').setActive(tn == 'A' && this.selectedNode.hasAttribute('href')); + btns.get('image').setActive(tn == 'IMG' || this.editorcore.enableBlocks && tn == 'FIGURE'); + btns.get('underline').setActive(tn == 'U' || sel.closest('u') ? true : false); - - // hides menus... - so this cant be on a menu... - Roo.bootstrap.MenuMgr.hideAll(); - */ Roo.bootstrap.menu.Manager.hideAll(); + + + + + + // handle delete button.. + if (hasToggle || (tn.length && tn == 'BODY')) { + this.deleteBtn.hide(); + return; + + } + this.deleteBtn.show(); + + + //this.editorsyncValue(); }, onFirstFocus: function() { @@ -32551,6 +33345,61 @@ Roo.extend(Roo.bootstrap.form.HtmlEditorToolbarStandard, Roo.bootstrap.nav.Simpl item.enable(); }); }, + + onDelete : function() + { + var range = this.editorcore.createRange(); + var selection = this.editorcore.getSelection(); + var sn = this.selectedNode; + range.setStart(sn,0); + range.setEnd(sn,0); + + + if (sn.hasAttribute('data-block')) { + var block = Roo.htmleditor.Block.factory(this.selectedNode); + if (block) { + sn = block.removeNode(); + sn.parentNode.removeChild(sn); + selection.removeAllRanges(); + selection.addRange(range); + this.updateToolbar(null, null, null); + if (sn.tagName.toUpperCase() == 'FIGURE') { + this.editor.syncValue(); + this.editor.fireEvent('imagedelete', this.editor, sn); + } + + this.selectedNode = false; + this.editorcore.fireEditorEvent(false); + return; + } + + } + if (!sn) { + return; // should not really happen.. + } + if (sn && sn.tagName == 'BODY') { + return; + } + var stn = sn.childNodes[0] || sn.nextSibling || sn.previousSibling || sn.parentNode; + + // remove and keep parents. + a = new Roo.htmleditor.FilterKeepChildren({tag : false}); + a.replaceTag(sn); + + selection.removeAllRanges(); + selection.addRange(range); + if (sn.tagName.toUpperCase() == 'IMG"') { + this.editor.syncValue(); + this.editor.fireEvent('imagedelete', this.editor, sn); + } + + this.selectedNode = false; + this.editorcore.fireEditorEvent(false); + + + }, + + toggleSourceEdit : function(sourceEditMode){