})(); 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.
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])));
}
}
: 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) {
* 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} summaryFooterShow (true|false) generate tfoot for summary, default false
+ * @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
store : false,
loadMask : false,
footerShow : true,
- summaryFooterShow : false,
+ footerRow : false,
headerShow : true,
enableColumnResize: true,
disableAutoSize: false,
cfg.cn.push(this.renderBody());
- if(this.footerShow || this.summaryFooterShow){
+ if(this.footerShow || this.footerRow){
cfg.cn.push(this.renderFooter());
}
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();
}
}
- if(!this.footerShow && this.summaryFooterShow) {
+ if(!this.footerShow && this.footerRow) {
var tr = {
tag : 'tr',
};
for(var i = 0, len = cm.getColumnCount(); i < len; i++){
-
- var value = cm.config[i].summaryFooter;
-
+ 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,
- style: '',
- html: cm.config[i].summaryFooter
+ html: footer
};
tr.cn.push(td);
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.
getAutoCreate : function()
{
- var align = (!this.labelAlign) ? this.parentLabelAlign() : this.labelAlign;
var id = Roo.id();
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',
};
-
- 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.
*/
* @return {Mixed} value The field value
*/
getValue : function(){
-
var v = this.inputEl().getValue();
-
return v;
},
/**
}
- 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;
}
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');
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 {
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);
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) {
* 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
// 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
*/
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' : '');
* @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
* valid according to {@link Date#parseDate} (defaults to 'H:i').
*/
format : "H:i",
+ minuteStep : 1,
getAutoCreate : function()
{
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();
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();
},
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 = {
-
- node: false,
+Roo.rtf.Parser = function(text) {
+ //super({objectMode: true})
+ this.text = '';
+ this.parserState = this.parseText;
- tag: false,
-
- // overrride to do replace comments.
- replaceComment : false,
+ // these are for interpeter...
+ this.doc = {};
+ ///this.parserState = this.parseTop
+ this.groupStack = [];
+ this.hexStore = [];
+ this.doc = false;
- // overrride to do replace or do stuff with tags..
- replaceTag : false,
+ this.groups = []; // where we put the return.
- 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.
- case typeof(this.tag) == 'string' && this.tag == e.tagName: // array and it matches.
- if (this.replaceTag && false === this.replaceTag(e)) {
- return;
- }
- if (e.hasChildNodes()) {
- this.walk(e);
- }
- return;
-
- default: // tags .. that do not match.
- if (e.hasChildNodes()) {
- this.walk(e);
- }
- }
-
- }, this);
+ 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]);
+ }
- removeNodeKeepChildren : function( node)
- {
- ar = Array.from(node.childNodes);
+};
+Roo.rtf.Parser.prototype = {
+ text : '', // string being parsed..
+ controlWord : '',
+ controlWordParam : '',
+ hexChar : '',
+ doc : false,
+ group: false,
+ groupStack : false,
+ hexStore : false,
+
+
+ cpos : 0,
+ row : 1, // reportin?
+ col : 1, //
+
+
+ push : function (el)
+ {
+ var m = 'cmd'+ el.type;
+ if (typeof(this[m]) == 'undefined') {
+ Roo.log('invalid cmd:' + el.type);
+ return;
+ }
+ this[m](el);
+ //Roo.log(el);
+ },
+ flushHexStore : function()
+ {
+ if (this.hexStore.length < 1) {
+ return;
+ }
+ var hexstr = this.hexStore.map(
+ function(cmd) {
+ return cmd.value;
+ }).join('');
+
+ this.group.addContent( new Roo.rtf.Hex( hexstr ));
+
+
+ this.hexStore.splice(0)
+
+ },
+
+ cmdgroupstart : function()
+ {
+ this.flushHexStore();
+ if (this.group) {
+ this.groupStack.push(this.group);
+ }
+ // parent..
+ if (this.doc === false) {
+ this.group = this.doc = new Roo.rtf.Document();
+ return;
+
+ }
+ this.group = new Roo.rtf.Group(this.group);
+ },
+ cmdignorable : function()
+ {
+ this.flushHexStore();
+ this.group.ignorable = true;
+ },
+ cmdendparagraph : function()
+ {
+ this.flushHexStore();
+ this.group.addContent(new Roo.rtf.Paragraph());
+ },
+ cmdgroupend : function ()
+ {
+ this.flushHexStore();
+ var endingGroup = this.group;
+
+
+ this.group = this.groupStack.pop();
+ if (this.group) {
+ this.group.addChild(endingGroup);
+ }
+
+
+
+ var doc = this.group || this.doc;
+ //if (endingGroup instanceof FontTable) {
+ // doc.fonts = endingGroup.table
+ //} else if (endingGroup instanceof ColorTable) {
+ // doc.colors = endingGroup.table
+ //} else if (endingGroup !== this.doc && !endingGroup.get('ignorable')) {
+ if (endingGroup.ignorable === false) {
+ //code
+ this.groups.push(endingGroup);
+ // Roo.log( endingGroup );
+ }
+ //Roo.each(endingGroup.content, function(item)) {
+ // doc.addContent(item);
+ //}
+ //process.emit('debug', 'GROUP END', endingGroup.type, endingGroup.get('ignorable'))
+ //}
+ },
+ cmdtext : function (cmd)
+ {
+ this.flushHexStore();
+ if (!this.group) { // an RTF fragment, missing the {\rtf1 header
+ //this.group = this.doc
+ 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.
+ case typeof(this.tag) == 'string' && this.tag == e.tagName: // array and it matches.
+ if (this.replaceTag && false === this.replaceTag(e)) {
+ return;
+ }
+ if (e.hasChildNodes()) {
+ this.walk(e);
+ }
+ return;
+
+ default: // tags .. that do not match.
+ if (e.hasChildNodes()) {
+ this.walk(e);
+ }
+ }
+
+ }, this);
+
+ },
+
+
+ removeNodeKeepChildren : function( node)
+ {
+
+ ar = Array.from(node.childNodes);
for (var i = 0; i < ar.length; i++) {
node.removeChild(ar[i]);
this.replaceAname(cfg.node);
// this is disabled as the removal is done by other filters;
// this.walk(cfg.node);
-
+ this.replaceImageTable(cfg.node);
}
parent = p.parentNode,
doc = parent.ownerDocument,
items = [];
-
+
+ //Roo.log("Parsing: " + p.innerText) ;
var listtype = 'ul';
while (ns) {
if (ns.nodeType != 1) {
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;
- }
-
+ },
+ replaceImageTable : function(doc)
+ {
+ /*
+ <table cellpadding=0 cellspacing=0 align=left>
+ <tr>
+ <td width=423 height=0></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td><img width=601 height=401
+ src="file:///C:/Users/Alan/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg"
+ v:shapes="Picture_x0020_2"></td>
+ </tr>
+ </table>
+ */
+ 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);
+
+
+
+ });
+
+
+ }
});
/**
}
},
-
+
{
xtype : 'Button',
text: 'Hide Caption',
var ret = {
tag: 'figure',
'data-block' : 'Figure',
- 'data-width' : this.width,
+ 'data-width' : this.width,
+ 'data-caption' : this.caption,
contenteditable : 'false',
style : {
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');
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');
* @param {Roo.HtmlEditorCore} this
*/
editorevent: true
-
+
});
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)
*/
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;
'rowspan',
'data-display',
'data-width',
+ 'data-caption',
'start' ,
'style',
// youtube embed.
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('<img src=" + url + ">');
+
+ var r = new FileReader();
+ var t = this;
+ r.addEventListener('load',function()
+ {
+
+ var d = (new DOMParser().parseFromString('<img src="' + r.result+ '">', '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 ) {
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..
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 &&
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;
*/
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,
* @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));
+
};
/**
- * @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
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)
*/
* 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);
-// }
},
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..
- }
+
},
//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);
},
-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
new Roo.bootstrap.form.HtmlEditor({
....
toolbars : [
- new Roo.bootstrap.form.HtmlEditorToolbarStandard({
+ new Roo.bootstrap.form.HtmlEditorToolbar.Standard({
disable : { fonts: 1 , format: 1, ..., ... , ...],
btns : [ .... ]
})
* .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);
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,
'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;
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';
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 : {}
size : 'sm',
xns: Roo.bootstrap,
fa : 'font',
+ cls : 'roo-html-editor-font-chooser',
//html : 'submit'
menu : {
xtype: 'Menu',
});
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);
}
}
- /*
- 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('<img src="' + reader.result +'">');
+ 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?
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() {
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){