X-Git-Url: http://git.roojs.org/?a=blobdiff_plain;f=Roo%2FHtmlEditorCore.js;h=eee4f0510ebe13a027ce581ca569b3b7b726b364;hb=825a74c132eae7b97e43866ab3c00ff122fb239c;hp=101e484256e910dae30567db882569cd7e7fbd3b;hpb=ebffc882d3118ac9a90868c3d37356b7e63fe75e;p=roojs1 diff --git a/Roo/HtmlEditorCore.js b/Roo/HtmlEditorCore.js index 101e484256..eee4f0510e 100644 --- a/Roo/HtmlEditorCore.js +++ b/Roo/HtmlEditorCore.js @@ -19,6 +19,8 @@ Roo.HtmlEditorCore = function(config){ Roo.HtmlEditorCore.superclass.constructor.call(this, config); + + this.addEvents({ /** * @event initialize @@ -70,8 +72,16 @@ Roo.HtmlEditorCore = function(config){ * @param {Roo.HtmlEditorCore} this */ editorevent: true + }); - + + // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements + + // defaults : white / black... + this.applyBlacklists(); + + + }; @@ -119,8 +129,11 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { clearUp: true, + // blacklist + whitelisted elements.. + black: false, + white: false, - + bodyCls : '', /** * Protected method that will not generally be called directly. It @@ -130,7 +143,6 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { getDocMarkup : function(){ // body styles.. var st = ''; - Roo.log(this.stylesheets); // inherit styels from page...?? if (this.stylesheets === false) { @@ -148,23 +160,27 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { st = ''; - } else { - Roo.each(this.stylesheets, function(s) { - st += '' - }); - + } else { + st = ''; } st += ''; + var cls = 'roo-htmleditor-body'; + + if(this.bodyCls.length){ + cls += ' ' + this.bodyCls; + } return '' + st + //' + - ' '; + ' '; }, // private @@ -232,8 +248,6 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { }; Roo.TaskMgr.start(task); - - }, // private @@ -505,7 +519,8 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { this.execCmd('FontSize', v ); }, - onEditorEvent : function(e){ + onEditorEvent : function(e) + { this.owner.fireEvent('editorevent', this, e); // this.updateToolbar(); this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff @@ -579,8 +594,6 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { insertAtCursor : function(text) { - - if(!this.activated){ return; } @@ -931,13 +944,16 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { var nodeIsBefore = ss == 1; var nodeIsAfter = ee == -1; - if (nodeIsBefore && nodeIsAfter) + if (nodeIsBefore && nodeIsAfter) { return 0; // outer - if (!nodeIsBefore && nodeIsAfter) + } + if (!nodeIsBefore && nodeIsAfter) { return 1; //right trailed. + } - if (nodeIsBefore && !nodeIsAfter) + if (nodeIsBefore && !nodeIsAfter) { return 2; // left trailed. + } // fully contined. return 3; }, @@ -996,8 +1012,11 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { // clean up silly Windows -- stuff? return; } + var lcname = node.tagName.toLowerCase(); + // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full + // whitelist of tags.. - if (Roo.HtmlEditorCore.black.indexOf(node.tagName.toLowerCase()) > -1 && this.clearUp) { + if (this.black.indexOf(lcname) > -1 && this.clearUp ) { // remove node. node.parentNode.removeChild(node); return; @@ -1036,7 +1055,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { if (v.match(/^\./) || v.match(/^\//)) { return; } - if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) { + if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/) || v.match(/^ftp:/)) { return; } if (v.match(/^#/)) { @@ -1047,15 +1066,15 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { } + var cwhite = this.cwhite; + var cblack = this.cblack; + function cleanStyle(n,v) { if (v.match(/expression/)) { //XSS?? should we even bother.. node.removeAttribute(n); return; } - var cwhite = typeof(ed.cwhite) != 'undefined' && ed.cwhite !== false ? ed.cwhite : Roo.HtmlEditorCore.cwhite; - var cblack = typeof(ed.cblack) != 'undefined' && ed.cwhite !== false ? ed.cblack : Roo.HtmlEditorCore.cblack; - var parts = v.split(/;/); var clean = []; @@ -1068,7 +1087,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { var l = p.split(':').shift().replace(/\s+/g,''); l = l.replace(/^\s+/g,'').replace(/\s+$/g,''); - if ( cblack.indexOf(l) > -1) { + if ( cwhite.length && cblack.indexOf(l) > -1) { // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v); //node.removeAttribute(n); return true; @@ -1125,7 +1144,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { node.className = ''; } - if (a.value.match(/body/)) { + if (a.value.match(/^body$/)) { node.className = ''; } continue; @@ -1141,27 +1160,29 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { }, + /** * Clean up MS wordisms... */ cleanWord : function(node) { - var _t = this; - var cleanWordChildren = function() - { - if (!node.childNodes.length) { - return; - } - for (var i = node.childNodes.length-1; i > -1 ; i--) { - _t.cleanWord(node.childNodes[i]); - } - } - - if (!node) { this.cleanWord(this.doc.body); return; } + + if( + node.nodeName == 'SPAN' && + !node.hasAttributes() && + node.childNodes.length == 1 && + node.firstChild.nodeName == "#text" + ) { + var text = ' ' + node.innerHTML + ' '; + var textNode = document.createTextNode(text); + node.parentNode.insertBefore(textNode, node); + node.parentNode.removeChild(node); + } + if (node.nodeName == "#text") { // clean up silly Windows -- stuff? return; @@ -1185,7 +1206,7 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { node.parentNode.insertBefore(cn, node); } node.parentNode.removeChild(node); - cleanWordChildren(); + this.iterateChildren(node, this.cleanWord); return; } // clean styles @@ -1229,114 +1250,334 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component, { node.removeAttribute('style'); } } + this.iterateChildren(node, this.cleanWord); - cleanWordChildren(); }, - domToHTML : function(currentElement, depth, nopadtext) { + /** + * iterateChildren of a Node, calling fn each time, using this as the scole.. + * @param {DomNode} node node to iterate children of. + * @param {Function} fn method of this class to call on each item. + */ + iterateChildren : function(node, fn) + { + if (!node.childNodes.length) { + return; + } + for (var i = node.childNodes.length-1; i > -1 ; i--) { + fn.call(this, node.childNodes[i]) + } + }, + + + /** + * cleanTableWidths. + * + * Quite often pasting from word etc.. results in tables with column and widths. + * This does not work well on fluid HTML layouts - like emails. - so this code should hunt an destroy them.. + * + */ + cleanTableWidths : function(node) + { + + + if (!node) { + this.cleanTableWidths(this.doc.body); + return; + } - depth = depth || 0; - nopadtext = nopadtext || false; + // ignore list... + if (node.nodeName == "#text" || node.nodeName == "#comment") { + return; + } + Roo.log(node.tagName); + if (!node.tagName.toLowerCase().match(/^(table|td|tr)$/)) { + this.iterateChildren(node, this.cleanTableWidths); + return; + } + if (node.hasAttribute('width')) { + node.removeAttribute('width'); + } - if (!currentElement) { - return this.domToHTML(this.doc.body); - } - - //Roo.log(currentElement); - var j; - var allText = false; - var nodeName = currentElement.nodeName; - var tagName = Roo.util.Format.htmlEncode(currentElement.tagName); + + if (node.hasAttribute("style")) { + // pretty basic... - if (nodeName == '#text') { - return currentElement.nodeValue; + var styles = node.getAttribute("style").split(";"); + var nstyle = []; + Roo.each(styles, function(s) { + if (!s.match(/:/)) { + return; + } + var kv = s.split(":"); + if (kv[0].match(/^\s*(width|min-width)\s*$/)) { + return; + } + // what ever is left... we allow. + nstyle.push(s); + }); + node.setAttribute("style", nstyle.length ? nstyle.join(';') : ''); + if (!nstyle.length) { + node.removeAttribute('style'); } + } + + this.iterateChildren(node, this.cleanTableWidths); + + + }, + + + + + domToHTML : function(currentElement, depth, nopadtext) { + + depth = depth || 0; + nopadtext = nopadtext || false; + + if (!currentElement) { + return this.domToHTML(this.doc.body); + } + + //Roo.log(currentElement); + var j; + var allText = false; + var nodeName = currentElement.nodeName; + var tagName = Roo.util.Format.htmlEncode(currentElement.tagName); + + if (nodeName == '#text') { - - var ret = ''; - if (nodeName != 'BODY') { - - var i = 0; - // Prints the node tagName, such as , , etc - if (tagName) { - var attr = []; - for(i = 0; i < currentElement.attributes.length;i++) { - // quoting? - var aname = currentElement.attributes.item(i).name; - if (!currentElement.attributes.item(i).value.length) { - continue; - } - attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' ); + return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim(); + } + + + var ret = ''; + if (nodeName != 'BODY') { + + var i = 0; + // Prints the node tagName, such as , , etc + if (tagName) { + var attr = []; + for(i = 0; i < currentElement.attributes.length;i++) { + // quoting? + var aname = currentElement.attributes.item(i).name; + if (!currentElement.attributes.item(i).value.length) { + continue; } - - ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">"; - } - else { - - // eack + attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' ); } - } else { - tagName = false; - } - if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) { - return ret; + + ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">"; + } + else { + + // eack } - if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code? - nopadtext = true; + } else { + tagName = false; + } + if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) { + return ret; + } + if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code? + nopadtext = true; + } + + + // Traverse the tree + i = 0; + var currentElementChild = currentElement.childNodes.item(i); + var allText = true; + var innerHTML = ''; + lastnode = ''; + while (currentElementChild) { + // Formatting code (indent the tree so it looks nice on the screen) + var nopad = nopadtext; + if (lastnode == 'SPAN') { + nopad = true; } - - - // Traverse the tree - i = 0; - var currentElementChild = currentElement.childNodes.item(i); - var allText = true; - var innerHTML = ''; - lastnode = ''; - while (currentElementChild) { - // Formatting code (indent the tree so it looks nice on the screen) - var nopad = nopadtext; - if (lastnode == 'SPAN') { - nopad = true; + // text + if (currentElementChild.nodeName == '#text') { + var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue); + toadd = nopadtext ? toadd : toadd.trim(); + if (!nopad && toadd.length > 80) { + innerHTML += "\n" + (new Array( depth + 1 )).join( " " ); } - // text - if (currentElementChild.nodeName == '#text') { - var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue); - if (!nopad && toadd.length > 80) { - innerHTML += "\n" + (new Array( depth + 1 )).join( " " ); - } - innerHTML += toadd; - - i++; - currentElementChild = currentElement.childNodes.item(i); - lastNode = ''; - continue; - } - allText = false; + innerHTML += toadd; - innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " ); - - // Recursively traverse the tree structure of the child node - innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext); - lastnode = currentElementChild.nodeName; i++; - currentElementChild=currentElement.childNodes.item(i); + currentElementChild = currentElement.childNodes.item(i); + lastNode = ''; + continue; } + allText = false; - ret += innerHTML; + innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " ); + + // Recursively traverse the tree structure of the child node + innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext); + lastnode = currentElementChild.nodeName; + i++; + currentElementChild=currentElement.childNodes.item(i); + } + + ret += innerHTML; + + if (!allText) { + // The remaining code is mostly for formatting the tree + ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " ); + } + + + if (tagName) { + ret+= ""; + } + return ret; + + }, + + applyBlacklists : function() + { + var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : []; + var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : []; + + this.white = []; + this.black = []; + Roo.each(Roo.HtmlEditorCore.white, function(tag) { + if (b.indexOf(tag) > -1) { + return; + } + this.white.push(tag); - if (!allText) { - // The remaining code is mostly for formatting the tree - ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " ); + }, this); + + Roo.each(w, function(tag) { + if (b.indexOf(tag) > -1) { + return; } + if (this.white.indexOf(tag) > -1) { + return; + } + this.white.push(tag); + }, this); + + + Roo.each(Roo.HtmlEditorCore.black, function(tag) { + if (w.indexOf(tag) > -1) { + return; + } + this.black.push(tag); - if (tagName) { - ret+= ""; + }, this); + + Roo.each(b, function(tag) { + if (w.indexOf(tag) > -1) { + return; } - return ret; + if (this.black.indexOf(tag) > -1) { + return; + } + this.black.push(tag); + + }, this); + + + w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : []; + b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : []; + + this.cwhite = []; + this.cblack = []; + Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) { + if (b.indexOf(tag) > -1) { + return; + } + this.cwhite.push(tag); + + }, this); + + Roo.each(w, function(tag) { + if (b.indexOf(tag) > -1) { + return; + } + if (this.cwhite.indexOf(tag) > -1) { + return; + } + this.cwhite.push(tag); + + }, this); + + + Roo.each(Roo.HtmlEditorCore.cblack, function(tag) { + if (w.indexOf(tag) > -1) { + return; + } + this.cblack.push(tag); + + }, this); + + Roo.each(b, function(tag) { + if (w.indexOf(tag) > -1) { + return; + } + if (this.cblack.indexOf(tag) > -1) { + return; + } + this.cblack.push(tag); + }, this); + }, + + setStylesheets : function(stylesheets) + { + if(typeof(stylesheets) == 'string'){ + Roo.get(this.iframe.contentDocument.head).createChild({ + tag : 'link', + rel : 'stylesheet', + type : 'text/css', + href : stylesheets + }); + + return; } + var _this = this; + + Roo.each(stylesheets, function(s) { + if(!s.length){ + return; + } + + Roo.get(_this.iframe.contentDocument.head).createChild({ + tag : 'link', + rel : 'stylesheet', + type : 'text/css', + href : s + }); + }); + + + }, + + removeStylesheets : function() + { + var _this = this; + + Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){ + s.remove(); + }); + }, + + setStyle : function(style) + { + Roo.get(this.iframe.contentDocument.head).createChild({ + tag : 'style', + type : 'text/css', + html : style + }); + + return; + } // hide stuff that is not compatible /**