var cn = Array.from(node.childNodes);
var nn = node;
Roo.each(inject, function(t) {
- var nc = node.ownerDocument.createelement(t);
+ var nc = node.ownerDocument.createElement(t);
nn.appendChild(nc);
nn = nc;
});
var cc = Roo.htmleditor.Block.cache;
var id = Roo.get(node).id;
if (typeof(cc[id]) != 'undefined' && (!cc[id].node || cc[id].node.closest('body'))) {
- Roo.htmleditor.Block.cache[id].readElement();
+ Roo.htmleditor.Block.cache[id].readElement(node);
return Roo.htmleditor.Block.cache[id];
}
var db = node.getAttribute('data-block');
// but kiss for now.
n = node.getElementsByTagName(tag).item(0);
}
+ if (!n) {
+ return '';
+ }
if (attr == 'html') {
return n.innerHTML;
}
if (attr == 'style') {
- return n.style[style]
+ return n.style[style];
}
- return Roo.get(n).attr(attr);
+ return n.hasAttribute(attr) ? n.getAttribute(attr) : '';
},
/**
* Block that has an image and a figcaption
* @cfg {String} image_src the url for the image
* @cfg {String} align (left|right) alignment for the block default left
- * @cfg {String} text_align (left|right) alignment for the text caption default left.
* @cfg {String} caption the text to appear below (and in the alt tag)
+ * @cfg {String} caption_display (block|none) display or not the caption
* @cfg {String|number} image_width the width of the image number or %?
* @cfg {String|number} image_height the height of the image number or %?
*
// setable values.
image_src: '',
-
align: 'center',
caption : '',
- text_align: 'left',
-
+ caption_display : 'block',
width : '100%',
- margin: '2%',
+ cls : '',
+ href: '',
+ video_url : '',
+
+ // margin: '2%', not used
+
+ text_align: 'left', // (left|right) alignment for the text caption default left. - not used at present
+
// used by context menu
friendly_name : 'Image with caption',
deleteTitle : "Delete Image and Caption",
-
contextMenu : function(toolbar)
{
return [
{
+ xtype : 'TextItem',
+ text : "Source: ",
+ xns : rooui.Toolbar //Boostrap?
+ },
+ {
+ xtype : 'Button',
+ text: 'Change Image URL',
+
+ listeners : {
+ click: function (btn, state)
+ {
+ var b = block();
+
+ Roo.MessageBox.show({
+ title : "Image Source URL",
+ msg : "Enter the url for the image",
+ buttons: Roo.MessageBox.OKCANCEL,
+ fn: function(btn, val){
+ if (btn != 'ok') {
+ return;
+ }
+ b.image_src = val;
+ b.updateElement();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
+ },
+ minWidth:250,
+ prompt:true,
+ //multiline: multiline,
+ modal : true,
+ value : b.image_src
+ });
+ }
+ },
+ xns : rooui.Toolbar
+ },
+
+ {
+ xtype : 'Button',
+ text: 'Change Link URL',
+
+ listeners : {
+ click: function (btn, state)
+ {
+ var b = block();
+
+ Roo.MessageBox.show({
+ title : "Link URL",
+ msg : "Enter the url for the link - leave blank to have no link",
+ buttons: Roo.MessageBox.OKCANCEL,
+ fn: function(btn, val){
+ if (btn != 'ok') {
+ return;
+ }
+ b.href = val;
+ b.updateElement();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
+ },
+ minWidth:250,
+ prompt:true,
+ //multiline: multiline,
+ modal : true,
+ value : b.href
+ });
+ }
+ },
+ xns : rooui.Toolbar
+ },
+ {
+ xtype : 'Button',
+ text: 'Show Video URL',
+
+ listeners : {
+ click: function (btn, state)
+ {
+ Roo.MessageBox.alert("Video URL",
+ block().video_url == '' ? 'This image is not linked ot a video' :
+ 'The image is linked to: <a target="_new" href="' + block().video_url + '">' + block().video_url + '</a>');
+ }
+ },
+ xns : rooui.Toolbar
+ },
+
+
+ {
xtype : 'TextItem',
text : "Width: ",
xns : rooui.Toolbar //Boostrap?
triggerAction : 'all',
typeAhead : true,
valueField : 'val',
- width : 100,
+ width : 70,
name : 'width',
listeners : {
select : function (combo, r, index)
triggerAction : 'all',
typeAhead : true,
valueField : 'val',
- width : 100,
- name : 'width',
+ width : 70,
+ name : 'align',
listeners : {
select : function (combo, r, index)
{
xns : Roo.data
}
},
+
+
{
- xtype : 'TextItem',
- text : "Image Source: ",
- xns : rooui.Toolbar //Boostrap?
- },
- {
- xtype : 'TextField',
- allowBlank : false,
- width : 100,
- name : 'width',
+ xtype : 'Button',
+ text: 'Hide Caption',
+ name : 'caption_display',
+ pressed : false,
+ enableToggle : true,
+ setValue : function(v) {
+ this.toggle(v == 'block' ? false : true);
+ },
listeners : {
- change : function (combo, r, index)
+ toggle: function (btn, state)
{
- return;
- toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
- var b = block();
- b.align = r.get('val');
+ var b = block();
+ b.caption_display = b.caption_display == 'block' ? 'none' : 'block';
+ this.setText(b.caption_display == 'block' ? "Hide Caption" : "Show Caption");
b.updateElement();
syncValue();
+ toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
toolbar.editorcore.onEditorEvent();
}
},
- xns : rooui.form
-
+ xns : rooui.Toolbar
}
];
var d = document.createElement('div');
d.innerHTML = this.caption;
- return {
+ var m = this.width == '50%' && this.align == 'center' ? '0 auto' : 0;
+
+ var img = {
+ tag : 'img',
+ contenteditable : 'false',
+ src : this.image_src,
+ alt : d.innerText.replace(/\n/g, " ").replace(/\s+/g, ' ').trim(), // removeHTML and reduce spaces..
+ style: {
+ width : 'auto',
+ 'max-width': '100%',
+ margin : '0px'
+
+
+ }
+ };
+ /*
+ '<div class="{0}" width="420" height="315" src="{1}" frameborder="0" allowfullscreen>' +
+ '<a href="{2}">' +
+ '<img class="{0}-thumbnail" src="{3}/Images/{4}/{5}#image-{4}" />' +
+ '</a>' +
+ '</div>',
+ */
+
+ if (this.href.length > 0) {
+ img = {
+ tag : 'a',
+ href: this.href,
+ contenteditable : 'true',
+ cn : [
+ img
+ ]
+ };
+ }
+
+
+ if (this.video_url.length > 0) {
+ img = {
+ tag : 'div',
+ cls : this.cls,
+ frameborder : 0,
+ allowfullscreen : true,
+ width : 420, // these are for video tricks - that we replace the outer
+ height : 315,
+ src : this.video_url,
+ cn : [
+ img
+ ]
+ };
+ }
+
+ return {
tag: 'figure',
'data-block' : 'Figure',
contenteditable : 'false',
style : {
- display: 'table',
+ display: 'block',
float : this.align ,
- width : this.width,
- margin: this.margin
+ 'max-width': this.width,
+ width : 'auto',
+ margin: m,
+ padding: '10px'
+
},
+
+
+ align : this.align,
cn : [
- {
- tag : 'img',
- src : this.image_src,
- alt : d.innerText.replace(/\n/g, " "), // removeHTML..
- style: {
- width: '100%'
- }
- },
+ img,
+
{
tag: 'figcaption',
contenteditable : true,
style : {
- 'text-align': this.text_align
+ 'text-align': 'left',
+ 'margin-top' : '16px',
+ 'font-size' : '16px',
+ 'line-height' : '24px',
+ 'font-style': 'italic',
+ display : this.caption_display
},
+ cls : this.cls.length > 0 ? (this.cls + '-thumbnail' ) : '',
html : this.caption
}
]
};
+
},
readElement : function(node)
{
+ // this should not really come from the link...
+ this.video_url = this.getVal(node, 'div', 'src');
+ this.cls = this.getVal(node, 'div', 'class');
+ this.href = this.getVal(node, 'a', 'href');
+
this.image_src = this.getVal(node, 'img', 'src');
- this.align = this.getVal(node, 'figure', 'style', 'float');
+
+ this.align = this.getVal(node, 'figure', 'align');
this.caption = this.getVal(node, 'figcaption', 'html');
- this.text_align = this.getVal(node, 'figcaption', 'style','text-align');
- this.width = this.getVal(node, 'figure', 'style', 'width');
- this.margin = this.getVal(node, 'figure', 'style', 'margin');
+ //this.text_align = this.getVal(node, 'figcaption', 'style','text-align');
+ this.width = this.getVal(node, 'figure', 'style', 'max-width');
+ //this.margin = this.getVal(node, 'figure', 'style', 'margin');
- }
+ },
+ removeNode : function()
+ {
+ return this.node;
+ }
var fields = {};
return [
+ {
+ xtype : 'TextItem',
+ text : "Width: ",
+ xns : rooui.Toolbar //Boostrap?
+ },
{
xtype : 'ComboBox',
allowBlank : false,
this.rows = [];
this.no_row = 0;
- var trs = Array.from(node.getElementsByTagName('tr'));
+ var trs = Array.from(node.rows);
trs.forEach(function(tr) {
var row = [];
this.rows.push(row);
- if (Roo.get(tr).hasClass('roo-html-editor-el')) { // ??? this is for our 'row' selection'
- return;
- }
+
this.no_row++;
var no_column = 0;
- Array.from(tr.getElementsByTagName('td')).forEach(function(td) {
- if (Roo.get(td).hasClass('roo-html-editor-el')) { // ??? this is for our 'row' selection'
- return;
- }
+ Array.from(tr.cells).forEach(function(td) {
+
var add = {
- colspan : td.hasAttribute('colspan') ? td.getAttribute('colspan') : 1,
- rowspan : td.hasAttribute('rowspan') ? td.getAttribute('rowspan') : 1,
+ colspan : td.hasAttribute('colspan') ? td.getAttribute('colspan')*1 : 1,
+ rowspan : td.hasAttribute('rowspan') ? td.getAttribute('rowspan')*1 : 1,
style : td.hasAttribute('style') ? td.getAttribute('style') : '',
html : td.innerHTML
};
addRow : function()
{
- row = [];
+ var row = [];
for (var i = 0; i < this.no_col; i++ ) {
row.push(this.emptyCell());
width: '',
textAlign : 'left',
+ valign : 'top',
colspan : 1,
rowspan : 1,
// used by context menu
friendly_name : 'Table Cell',
- deleteTitle : 'Delete Table',
+ deleteTitle : false, // use our customer delete
// context menu is drawn once..
},
xns : rooui.Toolbar
},
+
+ {
+ xtype : 'TextItem',
+ text : "Vertical Align: ",
+ xns : rooui.Toolbar //Boostrap?
+ },
+ {
+ xtype : 'ComboBox',
+ allowBlank : false,
+ displayField : 'val',
+ editable : true,
+ listWidth : 100,
+ triggerAction : 'all',
+ typeAhead : true,
+ valueField : 'val',
+ width : 100,
+ name : 'valign',
+ listeners : {
+ select : function (combo, r, index)
+ {
+ toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+ var b = cell();
+ b.valign = r.get('val');
+ b.updateElement();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
+ }
+ },
+ xns : rooui.form,
+ store : {
+ xtype : 'SimpleStore',
+ data : [
+ ['top'],
+ ['middle'],
+ ['bottom'] // there are afew more...
+ ],
+ fields : [ 'val'],
+ xns : Roo.data
+ }
+ },
+
{
xtype : 'TextItem',
text : "Merge Cells: ",
}
},
xns : rooui.Toolbar
+ },
+ {
+ xtype : 'Fill',
+ xns : rooui.Toolbar
+
+ },
+
+
+ {
+ xtype : 'Button',
+ text: 'Delete',
+
+ xns : rooui.Toolbar,
+ menu : {
+ xtype : 'Menu',
+ xns : rooui.menu,
+ items : [
+ {
+ xtype : 'Item',
+ html: 'Column',
+ listeners : {
+ click : function (_self, e)
+ {
+ var t = table();
+
+ cell().deleteColumn();
+ syncValue();
+ toolbar.editorcore.selectNode(t.node);
+ toolbar.editorcore.onEditorEvent();
+ }
+ },
+ xns : rooui.menu
+ },
+ {
+ xtype : 'Item',
+ html: 'Row',
+ listeners : {
+ click : function (_self, e)
+ {
+ var t = table();
+ cell().deleteRow();
+ syncValue();
+
+ toolbar.editorcore.selectNode(t.node);
+ toolbar.editorcore.onEditorEvent();
+
+ }
+ },
+ xns : rooui.menu
+ },
+ {
+ xtype : 'Separator',
+ xns : rooui.menu
+ },
+ {
+ xtype : 'Item',
+ html: 'Table',
+ listeners : {
+ click : function (_self, e)
+ {
+ var t = table();
+ var nn = t.node.nextSibling || t.node.previousSibling;
+ t.node.parentNode.removeChild(t.node);
+ if (nn) {
+ toolbar.editorcore.selectNode(nn, true);
+ }
+ toolbar.editorcore.onEditorEvent();
+
+ }
+ },
+ xns : rooui.menu
+ }
+ ]
+ }
}
+
// align... << fixme
];
tag : 'td',
contenteditable : 'true', // this stops cell selection from picking the table.
'data-block' : 'Td',
- width: this.width,
+ valign : this.valign,
style : {
- width: this.width,
'text-align' : this.textAlign,
border : 'solid 1px rgb(0, 0, 0)', // ??? hard coded?
'border-collapse' : 'collapse',
- padding : '6px' // 8 for desktop / 4 for mobile
+ padding : '6px', // 8 for desktop / 4 for mobile
+ 'vertical-align': this.valign
},
html : this.html
};
+ if (this.width != '') {
+ ret.width = this.width;
+ ret.style.width = this.width;
+ }
+
if (this.colspan > 1) {
- ret.colspan = cell.colspan ;
+ ret.colspan = this.colspan ;
}
- if (ret.rowspan > 1) {
- this.rowspan = cell.rowspan ;
+ if (this.rowspan > 1) {
+ ret.rowspan = this.rowspan ;
}
{
node = node ? node : this.node ;
this.width = node.style.width;
-
+ this.colspan = Math.max(1,1*node.getAttribute('colspan'));
+ this.rowspan = Math.max(1,1*node.getAttribute('rowspan'));
this.html = node.innerHTML;
removeNode : function()
{
return this.node.closest('table');
-
-
+
},
cellData : false,
var tab = this.node.closest('tr').closest('table');
+ var ctr = tab.rows[0].parentNode;
Array.from(tab.rows).forEach(function(r, ri){
+
Array.from(r.cells).forEach(function(ce, ci){
ce.parentNode.removeChild(ce);
});
+ r.parentNode.removeChild(r);
});
for(var r = 0 ; r < table.length; r++) {
var re = tab.rows[r];
+
+ var re = tab.ownerDocument.createElement('tr');
+ ctr.appendChild(re);
for(var c = 0 ; c < table[r].length; c++) {
if (table[r][c].cell === false) {
continue;
}, this);
this.updateWidths(table);
+ },
+ deleteRow : function()
+ {
+ // delete this rows 'tr'
+ // if any of the cells in this row have a rowspan > 1 && row!= this row..
+ // then reduce the rowspan.
+ var table = this.toTableArray();
+ // this.cellData.row;
+ for (var i =0;i< table[this.cellData.row].length ; i++) {
+ var c = table[this.cellData.row][i];
+ if (c.row != this.cellData.row) {
+
+ c.rowspan--;
+ c.cell.setAttribute('rowspan', c.rowspan);
+ continue;
+ }
+ if (c.rowspan > 1) {
+ c.rowspan--;
+ c.cell.setAttribute('rowspan', c.rowspan);
+ }
+ }
+ table.splice(this.cellData.row,1);
+ this.redrawAllCells(table);
+
+ },
+ deleteColumn : function()
+ {
+ var table = this.toTableArray();
+
+ for (var i =0;i< table.length ; i++) {
+ var c = table[i][this.cellData.col];
+ if (c.col != this.cellData.col) {
+ table[i][this.cellData.col].colspan--;
+ } else if (c.colspan > 1) {
+ c.colspan--;
+ c.cell.setAttribute('colspan', c.colspan);
+ }
+ table[i].splice(this.cellData.col,1);
+ }
+
+ this.redrawAllCells(table);
}
+
+
})
//<script type="text/javascript">
* Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
* @param {Roo.HtmlEditorCore} this
*/
- editorevent: true
+ editorevent: true
+
});
* @cfg {Number} width (in pixels)
*/
width: 500,
+ /**
+ * @cfg {boolean} autoClean - default true - loading and saving will remove quite a bit of formating,
+ * if you are doing an email editor, this probably needs disabling, it's designed
+ */
+ autoClean: true,
+ /**
+ * @cfg {boolean} enableBlocks - default true - if the block editor (table and figure should be enabled)
+ */
+ enableBlocks : true,
/**
* @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
*
*/
stylesheets: false,
+ /**
+ * @cfg {String} language default en - language of text (usefull for rtl languages)
+ *
+ */
+ language: 'en',
/**
- * @cfg {boolean} allowComments - default false - allow comments in HTML source - by default they are stripped - if you are editing email you may need this.
+ * @cfg {boolean} allowComments - default false - allow comments in HTML source
+ * - by default they are stripped - if you are editing email you may need this.
*/
allowComments: false,
// id of frame..
st += '<style type="text/css">' +
'IMG { cursor: pointer } ' +
'</style>';
-
- var cls = 'roo-htmleditor-body';
+
+ st += '<meta name="google" content="notranslate">';
+
+ var cls = 'notranslate roo-htmleditor-body';
if(this.bodyCls.length){
cls += ' ' + this.bodyCls;
}
- return '<html><head>' + st +
+ return '<html class="notranslate" translate="no"><head>' + st +
//<style type="text/css">' +
//'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
//'</style>' +
* @param {String} html The HTML to be cleaned
* return {String} The cleaned HTML
*/
- cleanHtml : function(html){
+ cleanHtml : function(html)
+ {
html = String(html);
if(html.length > 5){
if(Roo.isSafari){ // strip safari nonsense
var bd = (this.doc.body || this.doc.documentElement);
- //this.cleanUpPaste(); -- this is done else where and causes havoc..
-
- // not sure if this is really the place for this
- // the blocks are synced occasionaly - since we currently dont add listeners on the blocks
- // this has to update attributes that get duped.. like alt and caption..
-
-
- //Roo.each(Roo.get(this.doc.body).query('*[data-block]'), function(e) {
- // Roo.htmleditor.Block.factory(e);
- //},this);
+
+ var sel = this.win.getSelection();
var div = document.createElement('div');
div.innerHTML = bd.innerHTML;
- // remove content editable. (blocks)
+ var gtx = div.getElementsByClassName('gtx-trans-icon'); // google translate - really annoying and difficult to get rid of.
+ if (gtx.length > 0) {
+ var rm = gtx.item(0).parentNode;
+ rm.parentNode.removeChild(rm);
+ }
-
+ if (this.enableBlocks) {
+ new Roo.htmleditor.FilterBlock({ node : div });
+ }
//?? tidy?
- new Roo.htmleditor.FilterBlock({ node : div });
+ var tidy = new Roo.htmleditor.TidySerializer({
+ inner: true
+ });
+ var html = tidy.serialize(div);
+
- var html = div.innerHTML;
if(Roo.isSafari){
var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
var m = bs ? bs.match(/text-align:(.*?);/i) : false;
this.el.dom.value = d.innerHTML;
this.owner.fireEvent('push', this, v);
}
+ if (this.autoClean) {
+ new Roo.htmleditor.FilterParagraph({node : this.doc.body}); // paragraphs
+ new Roo.htmleditor.FilterSpan({node : this.doc.body}); // empty spans
+ }
+
Roo.htmleditor.Block.initAll(this.doc.body);
+ this.updateLanguage();
var lc = this.doc.body.lastChild;
if (lc && lc.nodeType == 1 && lc.getAttribute("contenteditable") == "false") {
//var ss = this.el.getStyles( 'background-image', 'background-repeat');
//ss['background-attachment'] = 'fixed'; // w3c
dbody.bgProperties = 'fixed'; // ie
+ dbody.setAttribute("translate", "no");
+
//Roo.DomHelper.applyStyles(dbody, ss);
Roo.EventManager.on(this.doc, {
- //'mousedown': this.onEditorEvent,
+
'mouseup': this.onEditorEvent,
'dblclick': this.onEditorEvent,
'click': this.onEditorEvent,
this.owner.fireEvent('initialize', this);
this.pushValue();
},
-
+ // this is to prevent a href clicks resulting in a redirect?
+
onPasteEvent : function(e,v)
{
// I think we better assume paste is going to be a dirty load of rubish from word..
img.setAttribute('src', images[i]);
});
}
-
-
- new Roo.htmleditor.FilterStyleToTag({ node : d });
- new Roo.htmleditor.FilterAttributes({
- node : d,
- attrib_white : ['href', 'src', 'name', 'align'],
- attrib_clean : ['href', 'src' ]
- });
- new Roo.htmleditor.FilterBlack({ node : d, tag : this.black});
- // should be fonts..
- new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT' ]} );
- new Roo.htmleditor.FilterParagraph({ node : d });
- new Roo.htmleditor.FilterSpan({ node : d });
- new Roo.htmleditor.FilterLongBr({ node : d });
-
+ if (this.autoClean) {
+ new Roo.htmleditor.FilterStyleToTag({ node : d });
+ new Roo.htmleditor.FilterAttributes({
+ node : d,
+ attrib_white : ['href', 'src', 'name', 'align'],
+ attrib_clean : ['href', 'src' ]
+ });
+ new Roo.htmleditor.FilterBlack({ node : d, tag : this.black});
+ // should be fonts..
+ new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT' ]} );
+ new Roo.htmleditor.FilterParagraph({ node : d });
+ new Roo.htmleditor.FilterSpan({ node : d });
+ new Roo.htmleditor.FilterLongBr({ node : d });
+ }
+ if (this.enableBlocks) {
+
+ Array.from(d.getElementsByTagName('img')).forEach(function(img) {
+ if (img.closest('figure')) { // assume!! that it's aready
+ return;
+ }
+ var fig = new Roo.htmleditor.BlockFigure({
+ image_src : img.src
+ });
+ fig.updateElement(img); // replace it..
+
+ });
+ }
- this.insertAtCursor(d.innerHTML);
- Roo.htmleditor.Block.initAll(this.doc.body);
+ this.insertAtCursor(d.innerHTML.replace(/ /g,' '));
+ if (this.enableBlocks) {
+ Roo.htmleditor.Block.initAll(this.doc.body);
+ }
e.preventDefault();
onEditorEvent : function(e)
{
+
if (e && (e.ctrlKey || e.metaKey) && e.keyCode === 90) {
return; // we do not handle this.. (undo manager does..)
* Select a dom node
* @param {DomElement} node the node to select
*/
- selectNode : function(node)
+ selectNode : function(node, collapse)
{
var nodeRange = node.ownerDocument.createRange();
try {
} catch (e) {
nodeRange.selectNodeContents(node);
}
- //nodeRange.collapse(true);
+ if (collapse === true) {
+ nodeRange.collapse(true);
+ }
+ //
var s = this.win.getSelection();
s.removeAllRanges();
s.addRange(nodeRange);
// should we cache this!!!!
-
-
+
var range = this.createRange(this.getSelection()).cloneRange();
return nodes[0];
},
+
+
createRange: function(sel)
{
// this has strange effects when using with
},
+
+ updateLanguage : function()
+ {
+ if (!this.iframe || !this.iframe.contentDocument) {
+ return;
+ }
+ Roo.get(this.iframe.contentDocument.body).attr("lang", this.language);
+ },
+
+
removeStylesheets : function()
{
var _this = this;