1 //<script type="text/javascript">
4 * Based Ext JS Library 1.1.1
5 * Copyright(c) 2006-2007, Ext JS, LLC.
11 * @class Roo.HtmlEditorCore
12 * @extends Roo.Component
13 * Provides a the editing component for the HTML editors in Roo. (bootstrap and Roo.form)
15 * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
18 Roo.HtmlEditorCore = function(config){
21 Roo.HtmlEditorCore.superclass.constructor.call(this, config);
27 * Fires when the editor is fully initialized (including the iframe)
28 * @param {Roo.HtmlEditorCore} this
33 * Fires when the editor is first receives the focus. Any insertion must wait
34 * until after this event.
35 * @param {Roo.HtmlEditorCore} this
40 * Fires before the textarea is updated with content from the editor iframe. Return false
42 * @param {Roo.HtmlEditorCore} this
43 * @param {String} html
48 * Fires before the iframe editor is updated with content from the textarea. Return false
50 * @param {Roo.HtmlEditorCore} this
51 * @param {String} html
56 * Fires when the textarea is updated with content from the editor iframe.
57 * @param {Roo.HtmlEditorCore} this
58 * @param {String} html
63 * Fires when the iframe editor is updated with content from the textarea.
64 * @param {Roo.HtmlEditorCore} this
65 * @param {String} html
71 * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
72 * @param {Roo.HtmlEditorCore} this
78 // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
80 // defaults : white / black...
81 this.applyBlacklists();
88 Roo.extend(Roo.HtmlEditorCore, Roo.Component, {
92 * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field
98 * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
103 * @cfg {Number} height (in pixels)
107 * @cfg {Number} width (in pixels)
112 * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
120 // private properties
121 validationEvent : false,
125 sourceEditMode : false,
126 onFocus : Roo.emptyFn,
132 // blacklist + whitelisted elements..
139 * Protected method that will not generally be called directly. It
140 * is called when the editor initializes the iframe with HTML contents. Override this method if you
141 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
143 getDocMarkup : function(){
146 Roo.log(this.stylesheets);
148 // inherit styels from page...??
149 if (this.stylesheets === false) {
151 Roo.get(document.head).select('style').each(function(node) {
152 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
155 Roo.get(document.head).select('link').each(function(node) {
156 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
159 } else if (!this.stylesheets.length) {
161 st = '<style type="text/css">' +
162 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
165 Roo.each(this.stylesheets, function(s) {
166 st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
171 st += '<style type="text/css">' +
172 'IMG { cursor: pointer } ' +
176 return '<html><head>' + st +
177 //<style type="text/css">' +
178 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
180 ' </head><body class="roo-htmleditor-body"></body></html>';
184 onRender : function(ct, position)
187 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
188 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
191 this.el.dom.style.border = '0 none';
192 this.el.dom.setAttribute('tabIndex', -1);
193 this.el.addClass('x-hidden hide');
197 if(Roo.isIE){ // fix IE 1px bogus margin
198 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
202 this.frameId = Roo.id();
206 var iframe = this.owner.wrap.createChild({
208 cls: 'form-control', // bootstrap..
212 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
217 this.iframe = iframe.dom;
221 this.doc.designMode = 'on';
224 this.doc.write(this.getDocMarkup());
228 var task = { // must defer to wait for browser to be ready
230 //console.log("run task?" + this.doc.readyState);
232 if(this.doc.body || this.doc.readyState == 'complete'){
234 this.doc.designMode="on";
238 Roo.TaskMgr.stop(task);
239 this.initEditor.defer(10, this);
246 Roo.TaskMgr.start(task);
253 onResize : function(w, h)
255 Roo.log('resize: ' +w + ',' + h );
256 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
260 if(typeof w == 'number'){
262 this.iframe.style.width = w + 'px';
264 if(typeof h == 'number'){
266 this.iframe.style.height = h + 'px';
268 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
275 * Toggles the editor between standard and source edit mode.
276 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
278 toggleSourceEdit : function(sourceEditMode){
280 this.sourceEditMode = sourceEditMode === true;
282 if(this.sourceEditMode){
284 Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
287 Roo.get(this.iframe).removeClass(['x-hidden','hide']);
288 //this.iframe.className = '';
291 //this.setSize(this.owner.wrap.getSize());
292 //this.fireEvent('editmodechange', this, this.sourceEditMode);
299 * Protected method that will not generally be called directly. If you need/want
300 * custom HTML cleanup, this is the method you should override.
301 * @param {String} html The HTML to be cleaned
302 * return {String} The cleaned HTML
304 cleanHtml : function(html){
307 if(Roo.isSafari){ // strip safari nonsense
308 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
311 if(html == ' '){
318 * HTML Editor -> Textarea
319 * Protected method that will not generally be called directly. Syncs the contents
320 * of the editor iframe with the textarea.
322 syncValue : function(){
323 if(this.initialized){
324 var bd = (this.doc.body || this.doc.documentElement);
325 //this.cleanUpPaste(); -- this is done else where and causes havoc..
326 var html = bd.innerHTML;
328 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
329 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
331 html = '<div style="'+m[0]+'">' + html + '</div>';
334 html = this.cleanHtml(html);
335 // fix up the special chars.. normaly like back quotes in word...
336 // however we do not want to do this with chinese..
337 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
338 var cc = b.charCodeAt();
340 (cc >= 0x4E00 && cc < 0xA000 ) ||
341 (cc >= 0x3400 && cc < 0x4E00 ) ||
342 (cc >= 0xf900 && cc < 0xfb00 )
348 if(this.owner.fireEvent('beforesync', this, html) !== false){
349 this.el.dom.value = html;
350 this.owner.fireEvent('sync', this, html);
356 * Protected method that will not generally be called directly. Pushes the value of the textarea
357 * into the iframe editor.
359 pushValue : function(){
360 if(this.initialized){
361 var v = this.el.dom.value.trim();
367 if(this.owner.fireEvent('beforepush', this, v) !== false){
368 var d = (this.doc.body || this.doc.documentElement);
371 this.el.dom.value = d.innerHTML;
372 this.owner.fireEvent('push', this, v);
378 deferFocus : function(){
379 this.focus.defer(10, this);
384 if(this.win && !this.sourceEditMode){
391 assignDocWin: function()
393 var iframe = this.iframe;
396 this.doc = iframe.contentWindow.document;
397 this.win = iframe.contentWindow;
399 // if (!Roo.get(this.frameId)) {
402 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
403 // this.win = Roo.get(this.frameId).dom.contentWindow;
405 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
409 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
410 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
415 initEditor : function(){
416 //console.log("INIT EDITOR");
421 this.doc.designMode="on";
423 this.doc.write(this.getDocMarkup());
426 var dbody = (this.doc.body || this.doc.documentElement);
427 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
428 // this copies styles from the containing element into thsi one..
429 // not sure why we need all of this..
430 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
432 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
433 //ss['background-attachment'] = 'fixed'; // w3c
434 dbody.bgProperties = 'fixed'; // ie
435 //Roo.DomHelper.applyStyles(dbody, ss);
436 Roo.EventManager.on(this.doc, {
437 //'mousedown': this.onEditorEvent,
438 'mouseup': this.onEditorEvent,
439 'dblclick': this.onEditorEvent,
440 'click': this.onEditorEvent,
441 'keyup': this.onEditorEvent,
446 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
448 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
449 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
451 this.initialized = true;
453 this.owner.fireEvent('initialize', this);
458 onDestroy : function(){
464 //for (var i =0; i < this.toolbars.length;i++) {
465 // // fixme - ask toolbars for heights?
466 // this.toolbars[i].onDestroy();
469 //this.wrap.dom.innerHTML = '';
470 //this.wrap.remove();
475 onFirstFocus : function(){
480 this.activated = true;
483 if(Roo.isGecko){ // prevent silly gecko errors
485 var s = this.win.getSelection();
486 if(!s.focusNode || s.focusNode.nodeType != 3){
487 var r = s.getRangeAt(0);
488 r.selectNodeContents((this.doc.body || this.doc.documentElement));
493 this.execCmd('useCSS', true);
494 this.execCmd('styleWithCSS', false);
497 this.owner.fireEvent('activate', this);
501 adjustFont: function(btn){
502 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
503 //if(Roo.isSafari){ // safari
506 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
507 if(Roo.isSafari){ // safari
508 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
509 v = (v < 10) ? 10 : v;
510 v = (v > 48) ? 48 : v;
511 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
516 v = Math.max(1, v+adjust);
518 this.execCmd('FontSize', v );
521 onEditorEvent : function(e){
522 this.owner.fireEvent('editorevent', this, e);
523 // this.updateToolbar();
524 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
527 insertTag : function(tg)
529 // could be a bit smarter... -> wrap the current selected tRoo..
530 if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
532 range = this.createRange(this.getSelection());
533 var wrappingNode = this.doc.createElement(tg.toLowerCase());
534 wrappingNode.appendChild(range.extractContents());
535 range.insertNode(wrappingNode);
542 this.execCmd("formatblock", tg);
546 insertText : function(txt)
550 var range = this.createRange();
551 range.deleteContents();
552 //alert(Sender.getAttribute('label'));
554 range.insertNode(this.doc.createTextNode(txt));
560 * Executes a Midas editor command on the editor document and performs necessary focus and
561 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
562 * @param {String} cmd The Midas command
563 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
565 relayCmd : function(cmd, value){
567 this.execCmd(cmd, value);
568 this.owner.fireEvent('editorevent', this);
569 //this.updateToolbar();
570 this.owner.deferFocus();
574 * Executes a Midas editor command directly on the editor document.
575 * For visual commands, you should use {@link #relayCmd} instead.
576 * <b>This should only be called after the editor is initialized.</b>
577 * @param {String} cmd The Midas command
578 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
580 execCmd : function(cmd, value){
581 this.doc.execCommand(cmd, false, value === undefined ? null : value);
588 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
590 * @param {String} text | dom node..
592 insertAtCursor : function(text)
603 var r = this.doc.selection.createRange();
614 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
618 // from jquery ui (MIT licenced)
622 if (win.getSelection && win.getSelection().getRangeAt) {
623 range = win.getSelection().getRangeAt(0);
624 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
625 range.insertNode(node);
626 } else if (win.document.selection && win.document.selection.createRange) {
627 // no firefox support
628 var txt = typeof(text) == 'string' ? text : text.outerHTML;
629 win.document.selection.createRange().pasteHTML(txt);
631 // no firefox support
632 var txt = typeof(text) == 'string' ? text : text.outerHTML;
633 this.execCmd('InsertHTML', txt);
642 mozKeyPress : function(e){
644 var c = e.getCharCode(), cmd;
647 c = String.fromCharCode(c).toLowerCase();
661 this.cleanUpPaste.defer(100, this);
677 fixKeys : function(){ // load time branching for fastest keydown performance
680 var k = e.getKey(), r;
683 r = this.doc.selection.createRange();
686 r.pasteHTML('    ');
693 r = this.doc.selection.createRange();
695 var target = r.parentElement();
696 if(!target || target.tagName.toLowerCase() != 'li'){
698 r.pasteHTML('<br />');
704 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
705 this.cleanUpPaste.defer(100, this);
711 }else if(Roo.isOpera){
717 this.execCmd('InsertHTML','    ');
720 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
721 this.cleanUpPaste.defer(100, this);
726 }else if(Roo.isSafari){
732 this.execCmd('InsertText','\t');
736 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
737 this.cleanUpPaste.defer(100, this);
745 getAllAncestors: function()
747 var p = this.getSelectedNode();
750 a.push(p); // push blank onto stack..
751 p = this.getParentElement();
755 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
759 a.push(this.doc.body);
766 getSelection : function()
769 return Roo.isIE ? this.doc.selection : this.win.getSelection();
772 getSelectedNode: function()
774 // this may only work on Gecko!!!
776 // should we cache this!!!!
781 var range = this.createRange(this.getSelection()).cloneRange();
784 var parent = range.parentElement();
786 var testRange = range.duplicate();
787 testRange.moveToElementText(parent);
788 if (testRange.inRange(range)) {
791 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
794 parent = parent.parentElement;
799 // is ancestor a text element.
800 var ac = range.commonAncestorContainer;
801 if (ac.nodeType == 3) {
805 var ar = ac.childNodes;
808 var other_nodes = [];
809 var has_other_nodes = false;
810 for (var i=0;i<ar.length;i++) {
811 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
814 // fullly contained node.
816 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
821 // probably selected..
822 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
823 other_nodes.push(ar[i]);
827 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
832 has_other_nodes = true;
834 if (!nodes.length && other_nodes.length) {
837 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
843 createRange: function(sel)
845 // this has strange effects when using with
846 // top toolbar - not sure if it's a great idea.
847 //this.editor.contentWindow.focus();
848 if (typeof sel != "undefined") {
850 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
852 return this.doc.createRange();
855 return this.doc.createRange();
858 getParentElement: function()
862 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
864 var range = this.createRange(sel);
867 var p = range.commonAncestorContainer;
868 while (p.nodeType == 3) { // text node
879 * Range intersection.. the hard stuff...
883 * [ -- selected range --- ]
887 * if end is before start or hits it. fail.
888 * if start is after end or hits it fail.
890 * if either hits (but other is outside. - then it's not
896 // @see http://www.thismuchiknow.co.uk/?p=64.
897 rangeIntersectsNode : function(range, node)
899 var nodeRange = node.ownerDocument.createRange();
901 nodeRange.selectNode(node);
903 nodeRange.selectNodeContents(node);
906 var rangeStartRange = range.cloneRange();
907 rangeStartRange.collapse(true);
909 var rangeEndRange = range.cloneRange();
910 rangeEndRange.collapse(false);
912 var nodeStartRange = nodeRange.cloneRange();
913 nodeStartRange.collapse(true);
915 var nodeEndRange = nodeRange.cloneRange();
916 nodeEndRange.collapse(false);
918 return rangeStartRange.compareBoundaryPoints(
919 Range.START_TO_START, nodeEndRange) == -1 &&
920 rangeEndRange.compareBoundaryPoints(
921 Range.START_TO_START, nodeStartRange) == 1;
925 rangeCompareNode : function(range, node)
927 var nodeRange = node.ownerDocument.createRange();
929 nodeRange.selectNode(node);
931 nodeRange.selectNodeContents(node);
935 range.collapse(true);
937 nodeRange.collapse(true);
939 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
940 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
942 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
944 var nodeIsBefore = ss == 1;
945 var nodeIsAfter = ee == -1;
947 if (nodeIsBefore && nodeIsAfter)
949 if (!nodeIsBefore && nodeIsAfter)
950 return 1; //right trailed.
952 if (nodeIsBefore && !nodeIsAfter)
953 return 2; // left trailed.
958 // private? - in a new class?
959 cleanUpPaste : function()
961 // cleans up the whole document..
962 Roo.log('cleanuppaste');
964 this.cleanUpChildren(this.doc.body);
965 var clean = this.cleanWordChars(this.doc.body.innerHTML);
966 if (clean != this.doc.body.innerHTML) {
967 this.doc.body.innerHTML = clean;
972 cleanWordChars : function(input) {// change the chars to hex code
973 var he = Roo.HtmlEditorCore;
976 Roo.each(he.swapCodes, function(sw) {
977 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
979 output = output.replace(swapper, sw[1]);
986 cleanUpChildren : function (n)
988 if (!n.childNodes.length) {
991 for (var i = n.childNodes.length-1; i > -1 ; i--) {
992 this.cleanUpChild(n.childNodes[i]);
999 cleanUpChild : function (node)
1002 //console.log(node);
1003 if (node.nodeName == "#text") {
1004 // clean up silly Windows -- stuff?
1007 if (node.nodeName == "#comment") {
1008 node.parentNode.removeChild(node);
1009 // clean up silly Windows -- stuff?
1012 var lcname = node.tagName.toLowerCase();
1013 // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full
1014 // whitelist of tags..
1016 if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
1018 node.parentNode.removeChild(node);
1023 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1025 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1026 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1028 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1029 // remove_keep_children = true;
1032 if (remove_keep_children) {
1033 this.cleanUpChildren(node);
1034 // inserts everything just before this node...
1035 while (node.childNodes.length) {
1036 var cn = node.childNodes[0];
1037 node.removeChild(cn);
1038 node.parentNode.insertBefore(cn, node);
1040 node.parentNode.removeChild(node);
1044 if (!node.attributes || !node.attributes.length) {
1045 this.cleanUpChildren(node);
1049 function cleanAttr(n,v)
1052 if (v.match(/^\./) || v.match(/^\//)) {
1055 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1058 if (v.match(/^#/)) {
1061 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1062 node.removeAttribute(n);
1066 var cwhite = this.cwhite;
1067 var cblack = this.cblack;
1069 function cleanStyle(n,v)
1071 if (v.match(/expression/)) { //XSS?? should we even bother..
1072 node.removeAttribute(n);
1076 var parts = v.split(/;/);
1079 Roo.each(parts, function(p) {
1080 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1084 var l = p.split(':').shift().replace(/\s+/g,'');
1085 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1087 if ( cwhite.length && cblack.indexOf(l) > -1) {
1088 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1089 //node.removeAttribute(n);
1093 // only allow 'c whitelisted system attributes'
1094 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1095 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1096 //node.removeAttribute(n);
1107 node.setAttribute(n, clean.join(';'));
1109 node.removeAttribute(n);
1115 for (var i = node.attributes.length-1; i > -1 ; i--) {
1116 var a = node.attributes[i];
1119 if (a.name.toLowerCase().substr(0,2)=='on') {
1120 node.removeAttribute(a.name);
1123 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1124 node.removeAttribute(a.name);
1127 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1128 cleanAttr(a.name,a.value); // fixme..
1131 if (a.name == 'style') {
1132 cleanStyle(a.name,a.value);
1135 /// clean up MS crap..
1136 // tecnically this should be a list of valid class'es..
1139 if (a.name == 'class') {
1140 if (a.value.match(/^Mso/)) {
1141 node.className = '';
1144 if (a.value.match(/body/)) {
1145 node.className = '';
1156 this.cleanUpChildren(node);
1161 * Clean up MS wordisms...
1163 cleanWord : function(node)
1166 var cleanWordChildren = function()
1168 if (!node.childNodes.length) {
1171 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1172 _t.cleanWord(node.childNodes[i]);
1178 this.cleanWord(this.doc.body);
1181 if (node.nodeName == "#text") {
1182 // clean up silly Windows -- stuff?
1185 if (node.nodeName == "#comment") {
1186 node.parentNode.removeChild(node);
1187 // clean up silly Windows -- stuff?
1191 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1192 node.parentNode.removeChild(node);
1196 // remove - but keep children..
1197 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1198 while (node.childNodes.length) {
1199 var cn = node.childNodes[0];
1200 node.removeChild(cn);
1201 node.parentNode.insertBefore(cn, node);
1203 node.parentNode.removeChild(node);
1204 cleanWordChildren();
1208 if (node.className.length) {
1210 var cn = node.className.split(/\W+/);
1212 Roo.each(cn, function(cls) {
1213 if (cls.match(/Mso[a-zA-Z]+/)) {
1218 node.className = cna.length ? cna.join(' ') : '';
1220 node.removeAttribute("class");
1224 if (node.hasAttribute("lang")) {
1225 node.removeAttribute("lang");
1228 if (node.hasAttribute("style")) {
1230 var styles = node.getAttribute("style").split(";");
1232 Roo.each(styles, function(s) {
1233 if (!s.match(/:/)) {
1236 var kv = s.split(":");
1237 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1240 // what ever is left... we allow.
1243 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1244 if (!nstyle.length) {
1245 node.removeAttribute('style');
1249 cleanWordChildren();
1253 domToHTML : function(currentElement, depth, nopadtext) {
1256 nopadtext = nopadtext || false;
1258 if (!currentElement) {
1259 return this.domToHTML(this.doc.body);
1262 //Roo.log(currentElement);
1264 var allText = false;
1265 var nodeName = currentElement.nodeName;
1266 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1268 if (nodeName == '#text') {
1270 return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim();
1275 if (nodeName != 'BODY') {
1278 // Prints the node tagName, such as <A>, <IMG>, etc
1281 for(i = 0; i < currentElement.attributes.length;i++) {
1283 var aname = currentElement.attributes.item(i).name;
1284 if (!currentElement.attributes.item(i).value.length) {
1287 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1290 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1299 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1302 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1307 // Traverse the tree
1309 var currentElementChild = currentElement.childNodes.item(i);
1313 while (currentElementChild) {
1314 // Formatting code (indent the tree so it looks nice on the screen)
1315 var nopad = nopadtext;
1316 if (lastnode == 'SPAN') {
1320 if (currentElementChild.nodeName == '#text') {
1321 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1322 toadd = nopadtext ? toadd : toadd.trim();
1323 if (!nopad && toadd.length > 80) {
1324 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1329 currentElementChild = currentElement.childNodes.item(i);
1335 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1337 // Recursively traverse the tree structure of the child node
1338 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1339 lastnode = currentElementChild.nodeName;
1341 currentElementChild=currentElement.childNodes.item(i);
1347 // The remaining code is mostly for formatting the tree
1348 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1353 ret+= "</"+tagName+">";
1359 applyBlacklists : function()
1361 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1362 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1366 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1367 if (b.indexOf(tag) > -1) {
1370 this.white.push(tag);
1374 Roo.each(w, function(tag) {
1375 if (b.indexOf(tag) > -1) {
1378 if (this.white.indexOf(tag) > -1) {
1381 this.white.push(tag);
1386 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1387 if (w.indexOf(tag) > -1) {
1390 this.black.push(tag);
1394 Roo.each(b, function(tag) {
1395 if (w.indexOf(tag) > -1) {
1398 if (this.black.indexOf(tag) > -1) {
1401 this.black.push(tag);
1406 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1407 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1411 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1412 if (b.indexOf(tag) > -1) {
1415 this.cwhite.push(tag);
1419 Roo.each(w, function(tag) {
1420 if (b.indexOf(tag) > -1) {
1423 if (this.cwhite.indexOf(tag) > -1) {
1426 this.cwhite.push(tag);
1431 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1432 if (w.indexOf(tag) > -1) {
1435 this.cblack.push(tag);
1439 Roo.each(b, function(tag) {
1440 if (w.indexOf(tag) > -1) {
1443 if (this.cblack.indexOf(tag) > -1) {
1446 this.cblack.push(tag);
1451 setStylesheets : function(stylesheet)
1453 Roo.get(this.iframe.contentDocument.head).createChild({
1461 // hide stuff that is not compatible
1479 * @cfg {String} fieldClass @hide
1482 * @cfg {String} focusClass @hide
1485 * @cfg {String} autoCreate @hide
1488 * @cfg {String} inputType @hide
1491 * @cfg {String} invalidClass @hide
1494 * @cfg {String} invalidText @hide
1497 * @cfg {String} msgFx @hide
1500 * @cfg {String} validateOnBlur @hide
1504 Roo.HtmlEditorCore.white = [
1505 'area', 'br', 'img', 'input', 'hr', 'wbr',
1507 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1508 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1509 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1510 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1511 'table', 'ul', 'xmp',
1513 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1516 'dir', 'menu', 'ol', 'ul', 'dl',
1522 Roo.HtmlEditorCore.black = [
1523 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1525 'base', 'basefont', 'bgsound', 'blink', 'body',
1526 'frame', 'frameset', 'head', 'html', 'ilayer',
1527 'iframe', 'layer', 'link', 'meta', 'object',
1528 'script', 'style' ,'title', 'xml' // clean later..
1530 Roo.HtmlEditorCore.clean = [
1531 'script', 'style', 'title', 'xml'
1533 Roo.HtmlEditorCore.remove = [
1538 Roo.HtmlEditorCore.ablack = [
1542 Roo.HtmlEditorCore.aclean = [
1543 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1547 Roo.HtmlEditorCore.pwhite= [
1548 'http', 'https', 'mailto'
1551 // white listed style attributes.
1552 Roo.HtmlEditorCore.cwhite= [
1553 // 'text-align', /// default is to allow most things..
1559 // black listed style attributes.
1560 Roo.HtmlEditorCore.cblack= [
1561 // 'font-size' -- this can be set by the project
1565 Roo.HtmlEditorCore.swapCodes =[