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
77 // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
79 // defaults : white / black...
81 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
82 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
85 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
86 if (b.indexOf(tag) > -1) {
93 Roo.each(w, function(tag) {
94 if (b.indexOf(tag) > -1) {
97 if (this.white.indexOf(tag) > -1) {
100 this.white.push(tag);
109 Roo.extend(Roo.HtmlEditorCore, Roo.Component, {
113 * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field
119 * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
124 * @cfg {Number} height (in pixels)
128 * @cfg {Number} width (in pixels)
133 * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
141 // private properties
142 validationEvent : false,
146 sourceEditMode : false,
147 onFocus : Roo.emptyFn,
153 // blacklist + whitelisted elements..
160 * Protected method that will not generally be called directly. It
161 * is called when the editor initializes the iframe with HTML contents. Override this method if you
162 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
164 getDocMarkup : function(){
167 Roo.log(this.stylesheets);
169 // inherit styels from page...??
170 if (this.stylesheets === false) {
172 Roo.get(document.head).select('style').each(function(node) {
173 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
176 Roo.get(document.head).select('link').each(function(node) {
177 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
180 } else if (!this.stylesheets.length) {
182 st = '<style type="text/css">' +
183 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
186 Roo.each(this.stylesheets, function(s) {
187 st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
192 st += '<style type="text/css">' +
193 'IMG { cursor: pointer } ' +
197 return '<html><head>' + st +
198 //<style type="text/css">' +
199 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
201 ' </head><body class="roo-htmleditor-body"></body></html>';
205 onRender : function(ct, position)
208 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
209 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
212 this.el.dom.style.border = '0 none';
213 this.el.dom.setAttribute('tabIndex', -1);
214 this.el.addClass('x-hidden hide');
218 if(Roo.isIE){ // fix IE 1px bogus margin
219 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
223 this.frameId = Roo.id();
227 var iframe = this.owner.wrap.createChild({
229 cls: 'form-control', // bootstrap..
233 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
238 this.iframe = iframe.dom;
242 this.doc.designMode = 'on';
245 this.doc.write(this.getDocMarkup());
249 var task = { // must defer to wait for browser to be ready
251 //console.log("run task?" + this.doc.readyState);
253 if(this.doc.body || this.doc.readyState == 'complete'){
255 this.doc.designMode="on";
259 Roo.TaskMgr.stop(task);
260 this.initEditor.defer(10, this);
267 Roo.TaskMgr.start(task);
274 onResize : function(w, h)
276 Roo.log('resize: ' +w + ',' + h );
277 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
281 if(typeof w == 'number'){
283 this.iframe.style.width = w + 'px';
285 if(typeof h == 'number'){
287 this.iframe.style.height = h + 'px';
289 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
296 * Toggles the editor between standard and source edit mode.
297 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
299 toggleSourceEdit : function(sourceEditMode){
301 this.sourceEditMode = sourceEditMode === true;
303 if(this.sourceEditMode){
305 Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
308 Roo.get(this.iframe).removeClass(['x-hidden','hide']);
309 //this.iframe.className = '';
312 //this.setSize(this.owner.wrap.getSize());
313 //this.fireEvent('editmodechange', this, this.sourceEditMode);
320 * Protected method that will not generally be called directly. If you need/want
321 * custom HTML cleanup, this is the method you should override.
322 * @param {String} html The HTML to be cleaned
323 * return {String} The cleaned HTML
325 cleanHtml : function(html){
328 if(Roo.isSafari){ // strip safari nonsense
329 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
332 if(html == ' '){
339 * HTML Editor -> Textarea
340 * Protected method that will not generally be called directly. Syncs the contents
341 * of the editor iframe with the textarea.
343 syncValue : function(){
344 if(this.initialized){
345 var bd = (this.doc.body || this.doc.documentElement);
346 //this.cleanUpPaste(); -- this is done else where and causes havoc..
347 var html = bd.innerHTML;
349 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
350 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
352 html = '<div style="'+m[0]+'">' + html + '</div>';
355 html = this.cleanHtml(html);
356 // fix up the special chars.. normaly like back quotes in word...
357 // however we do not want to do this with chinese..
358 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
359 var cc = b.charCodeAt();
361 (cc >= 0x4E00 && cc < 0xA000 ) ||
362 (cc >= 0x3400 && cc < 0x4E00 ) ||
363 (cc >= 0xf900 && cc < 0xfb00 )
369 if(this.owner.fireEvent('beforesync', this, html) !== false){
370 this.el.dom.value = html;
371 this.owner.fireEvent('sync', this, html);
377 * Protected method that will not generally be called directly. Pushes the value of the textarea
378 * into the iframe editor.
380 pushValue : function(){
381 if(this.initialized){
382 var v = this.el.dom.value.trim();
388 if(this.owner.fireEvent('beforepush', this, v) !== false){
389 var d = (this.doc.body || this.doc.documentElement);
392 this.el.dom.value = d.innerHTML;
393 this.owner.fireEvent('push', this, v);
399 deferFocus : function(){
400 this.focus.defer(10, this);
405 if(this.win && !this.sourceEditMode){
412 assignDocWin: function()
414 var iframe = this.iframe;
417 this.doc = iframe.contentWindow.document;
418 this.win = iframe.contentWindow;
420 // if (!Roo.get(this.frameId)) {
423 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
424 // this.win = Roo.get(this.frameId).dom.contentWindow;
426 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
430 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
431 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
436 initEditor : function(){
437 //console.log("INIT EDITOR");
442 this.doc.designMode="on";
444 this.doc.write(this.getDocMarkup());
447 var dbody = (this.doc.body || this.doc.documentElement);
448 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
449 // this copies styles from the containing element into thsi one..
450 // not sure why we need all of this..
451 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
453 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
454 //ss['background-attachment'] = 'fixed'; // w3c
455 dbody.bgProperties = 'fixed'; // ie
456 //Roo.DomHelper.applyStyles(dbody, ss);
457 Roo.EventManager.on(this.doc, {
458 //'mousedown': this.onEditorEvent,
459 'mouseup': this.onEditorEvent,
460 'dblclick': this.onEditorEvent,
461 'click': this.onEditorEvent,
462 'keyup': this.onEditorEvent,
467 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
469 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
470 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
472 this.initialized = true;
474 this.owner.fireEvent('initialize', this);
479 onDestroy : function(){
485 //for (var i =0; i < this.toolbars.length;i++) {
486 // // fixme - ask toolbars for heights?
487 // this.toolbars[i].onDestroy();
490 //this.wrap.dom.innerHTML = '';
491 //this.wrap.remove();
496 onFirstFocus : function(){
501 this.activated = true;
504 if(Roo.isGecko){ // prevent silly gecko errors
506 var s = this.win.getSelection();
507 if(!s.focusNode || s.focusNode.nodeType != 3){
508 var r = s.getRangeAt(0);
509 r.selectNodeContents((this.doc.body || this.doc.documentElement));
514 this.execCmd('useCSS', true);
515 this.execCmd('styleWithCSS', false);
518 this.owner.fireEvent('activate', this);
522 adjustFont: function(btn){
523 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
524 //if(Roo.isSafari){ // safari
527 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
528 if(Roo.isSafari){ // safari
529 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
530 v = (v < 10) ? 10 : v;
531 v = (v > 48) ? 48 : v;
532 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
537 v = Math.max(1, v+adjust);
539 this.execCmd('FontSize', v );
542 onEditorEvent : function(e){
543 this.owner.fireEvent('editorevent', this, e);
544 // this.updateToolbar();
545 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
548 insertTag : function(tg)
550 // could be a bit smarter... -> wrap the current selected tRoo..
551 if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
553 range = this.createRange(this.getSelection());
554 var wrappingNode = this.doc.createElement(tg.toLowerCase());
555 wrappingNode.appendChild(range.extractContents());
556 range.insertNode(wrappingNode);
563 this.execCmd("formatblock", tg);
567 insertText : function(txt)
571 var range = this.createRange();
572 range.deleteContents();
573 //alert(Sender.getAttribute('label'));
575 range.insertNode(this.doc.createTextNode(txt));
581 * Executes a Midas editor command on the editor document and performs necessary focus and
582 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
583 * @param {String} cmd The Midas command
584 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
586 relayCmd : function(cmd, value){
588 this.execCmd(cmd, value);
589 this.owner.fireEvent('editorevent', this);
590 //this.updateToolbar();
591 this.owner.deferFocus();
595 * Executes a Midas editor command directly on the editor document.
596 * For visual commands, you should use {@link #relayCmd} instead.
597 * <b>This should only be called after the editor is initialized.</b>
598 * @param {String} cmd The Midas command
599 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
601 execCmd : function(cmd, value){
602 this.doc.execCommand(cmd, false, value === undefined ? null : value);
609 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
611 * @param {String} text | dom node..
613 insertAtCursor : function(text)
624 var r = this.doc.selection.createRange();
635 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
639 // from jquery ui (MIT licenced)
643 if (win.getSelection && win.getSelection().getRangeAt) {
644 range = win.getSelection().getRangeAt(0);
645 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
646 range.insertNode(node);
647 } else if (win.document.selection && win.document.selection.createRange) {
648 // no firefox support
649 var txt = typeof(text) == 'string' ? text : text.outerHTML;
650 win.document.selection.createRange().pasteHTML(txt);
652 // no firefox support
653 var txt = typeof(text) == 'string' ? text : text.outerHTML;
654 this.execCmd('InsertHTML', txt);
663 mozKeyPress : function(e){
665 var c = e.getCharCode(), cmd;
668 c = String.fromCharCode(c).toLowerCase();
682 this.cleanUpPaste.defer(100, this);
698 fixKeys : function(){ // load time branching for fastest keydown performance
701 var k = e.getKey(), r;
704 r = this.doc.selection.createRange();
707 r.pasteHTML('    ');
714 r = this.doc.selection.createRange();
716 var target = r.parentElement();
717 if(!target || target.tagName.toLowerCase() != 'li'){
719 r.pasteHTML('<br />');
725 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
726 this.cleanUpPaste.defer(100, this);
732 }else if(Roo.isOpera){
738 this.execCmd('InsertHTML','    ');
741 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
742 this.cleanUpPaste.defer(100, this);
747 }else if(Roo.isSafari){
753 this.execCmd('InsertText','\t');
757 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
758 this.cleanUpPaste.defer(100, this);
766 getAllAncestors: function()
768 var p = this.getSelectedNode();
771 a.push(p); // push blank onto stack..
772 p = this.getParentElement();
776 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
780 a.push(this.doc.body);
787 getSelection : function()
790 return Roo.isIE ? this.doc.selection : this.win.getSelection();
793 getSelectedNode: function()
795 // this may only work on Gecko!!!
797 // should we cache this!!!!
802 var range = this.createRange(this.getSelection()).cloneRange();
805 var parent = range.parentElement();
807 var testRange = range.duplicate();
808 testRange.moveToElementText(parent);
809 if (testRange.inRange(range)) {
812 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
815 parent = parent.parentElement;
820 // is ancestor a text element.
821 var ac = range.commonAncestorContainer;
822 if (ac.nodeType == 3) {
826 var ar = ac.childNodes;
829 var other_nodes = [];
830 var has_other_nodes = false;
831 for (var i=0;i<ar.length;i++) {
832 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
835 // fullly contained node.
837 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
842 // probably selected..
843 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
844 other_nodes.push(ar[i]);
848 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
853 has_other_nodes = true;
855 if (!nodes.length && other_nodes.length) {
858 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
864 createRange: function(sel)
866 // this has strange effects when using with
867 // top toolbar - not sure if it's a great idea.
868 //this.editor.contentWindow.focus();
869 if (typeof sel != "undefined") {
871 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
873 return this.doc.createRange();
876 return this.doc.createRange();
879 getParentElement: function()
883 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
885 var range = this.createRange(sel);
888 var p = range.commonAncestorContainer;
889 while (p.nodeType == 3) { // text node
900 * Range intersection.. the hard stuff...
904 * [ -- selected range --- ]
908 * if end is before start or hits it. fail.
909 * if start is after end or hits it fail.
911 * if either hits (but other is outside. - then it's not
917 // @see http://www.thismuchiknow.co.uk/?p=64.
918 rangeIntersectsNode : function(range, node)
920 var nodeRange = node.ownerDocument.createRange();
922 nodeRange.selectNode(node);
924 nodeRange.selectNodeContents(node);
927 var rangeStartRange = range.cloneRange();
928 rangeStartRange.collapse(true);
930 var rangeEndRange = range.cloneRange();
931 rangeEndRange.collapse(false);
933 var nodeStartRange = nodeRange.cloneRange();
934 nodeStartRange.collapse(true);
936 var nodeEndRange = nodeRange.cloneRange();
937 nodeEndRange.collapse(false);
939 return rangeStartRange.compareBoundaryPoints(
940 Range.START_TO_START, nodeEndRange) == -1 &&
941 rangeEndRange.compareBoundaryPoints(
942 Range.START_TO_START, nodeStartRange) == 1;
946 rangeCompareNode : function(range, node)
948 var nodeRange = node.ownerDocument.createRange();
950 nodeRange.selectNode(node);
952 nodeRange.selectNodeContents(node);
956 range.collapse(true);
958 nodeRange.collapse(true);
960 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
961 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
963 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
965 var nodeIsBefore = ss == 1;
966 var nodeIsAfter = ee == -1;
968 if (nodeIsBefore && nodeIsAfter)
970 if (!nodeIsBefore && nodeIsAfter)
971 return 1; //right trailed.
973 if (nodeIsBefore && !nodeIsAfter)
974 return 2; // left trailed.
979 // private? - in a new class?
980 cleanUpPaste : function()
982 // cleans up the whole document..
983 Roo.log('cleanuppaste');
985 this.cleanUpChildren(this.doc.body);
986 var clean = this.cleanWordChars(this.doc.body.innerHTML);
987 if (clean != this.doc.body.innerHTML) {
988 this.doc.body.innerHTML = clean;
993 cleanWordChars : function(input) {// change the chars to hex code
994 var he = Roo.HtmlEditorCore;
997 Roo.each(he.swapCodes, function(sw) {
998 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1000 output = output.replace(swapper, sw[1]);
1007 cleanUpChildren : function (n)
1009 if (!n.childNodes.length) {
1012 for (var i = n.childNodes.length-1; i > -1 ; i--) {
1013 this.cleanUpChild(n.childNodes[i]);
1020 cleanUpChild : function (node)
1023 //console.log(node);
1024 if (node.nodeName == "#text") {
1025 // clean up silly Windows -- stuff?
1028 if (node.nodeName == "#comment") {
1029 node.parentNode.removeChild(node);
1030 // clean up silly Windows -- stuff?
1034 if (Roo.HtmlEditorCore.black.indexOf(node.tagName.toLowerCase()) > -1 && this.clearUp) {
1036 node.parentNode.removeChild(node);
1041 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1043 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1044 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1046 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1047 // remove_keep_children = true;
1050 if (remove_keep_children) {
1051 this.cleanUpChildren(node);
1052 // inserts everything just before this node...
1053 while (node.childNodes.length) {
1054 var cn = node.childNodes[0];
1055 node.removeChild(cn);
1056 node.parentNode.insertBefore(cn, node);
1058 node.parentNode.removeChild(node);
1062 if (!node.attributes || !node.attributes.length) {
1063 this.cleanUpChildren(node);
1067 function cleanAttr(n,v)
1070 if (v.match(/^\./) || v.match(/^\//)) {
1073 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1076 if (v.match(/^#/)) {
1079 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1080 node.removeAttribute(n);
1084 function cleanStyle(n,v)
1086 if (v.match(/expression/)) { //XSS?? should we even bother..
1087 node.removeAttribute(n);
1090 var cwhite = typeof(ed.cwhite) != 'undefined' && ed.cwhite !== false ? ed.cwhite : Roo.HtmlEditorCore.cwhite;
1091 var cblack = typeof(ed.cblack) != 'undefined' && ed.cwhite !== false ? ed.cblack : Roo.HtmlEditorCore.cblack;
1094 var parts = v.split(/;/);
1097 Roo.each(parts, function(p) {
1098 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1102 var l = p.split(':').shift().replace(/\s+/g,'');
1103 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1105 if ( cblack.indexOf(l) > -1) {
1106 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1107 //node.removeAttribute(n);
1111 // only allow 'c whitelisted system attributes'
1112 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1113 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1114 //node.removeAttribute(n);
1125 node.setAttribute(n, clean.join(';'));
1127 node.removeAttribute(n);
1133 for (var i = node.attributes.length-1; i > -1 ; i--) {
1134 var a = node.attributes[i];
1137 if (a.name.toLowerCase().substr(0,2)=='on') {
1138 node.removeAttribute(a.name);
1141 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1142 node.removeAttribute(a.name);
1145 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1146 cleanAttr(a.name,a.value); // fixme..
1149 if (a.name == 'style') {
1150 cleanStyle(a.name,a.value);
1153 /// clean up MS crap..
1154 // tecnically this should be a list of valid class'es..
1157 if (a.name == 'class') {
1158 if (a.value.match(/^Mso/)) {
1159 node.className = '';
1162 if (a.value.match(/body/)) {
1163 node.className = '';
1174 this.cleanUpChildren(node);
1179 * Clean up MS wordisms...
1181 cleanWord : function(node)
1184 var cleanWordChildren = function()
1186 if (!node.childNodes.length) {
1189 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1190 _t.cleanWord(node.childNodes[i]);
1196 this.cleanWord(this.doc.body);
1199 if (node.nodeName == "#text") {
1200 // clean up silly Windows -- stuff?
1203 if (node.nodeName == "#comment") {
1204 node.parentNode.removeChild(node);
1205 // clean up silly Windows -- stuff?
1209 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1210 node.parentNode.removeChild(node);
1214 // remove - but keep children..
1215 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1216 while (node.childNodes.length) {
1217 var cn = node.childNodes[0];
1218 node.removeChild(cn);
1219 node.parentNode.insertBefore(cn, node);
1221 node.parentNode.removeChild(node);
1222 cleanWordChildren();
1226 if (node.className.length) {
1228 var cn = node.className.split(/\W+/);
1230 Roo.each(cn, function(cls) {
1231 if (cls.match(/Mso[a-zA-Z]+/)) {
1236 node.className = cna.length ? cna.join(' ') : '';
1238 node.removeAttribute("class");
1242 if (node.hasAttribute("lang")) {
1243 node.removeAttribute("lang");
1246 if (node.hasAttribute("style")) {
1248 var styles = node.getAttribute("style").split(";");
1250 Roo.each(styles, function(s) {
1251 if (!s.match(/:/)) {
1254 var kv = s.split(":");
1255 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1258 // what ever is left... we allow.
1261 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1262 if (!nstyle.length) {
1263 node.removeAttribute('style');
1267 cleanWordChildren();
1271 domToHTML : function(currentElement, depth, nopadtext) {
1274 nopadtext = nopadtext || false;
1276 if (!currentElement) {
1277 return this.domToHTML(this.doc.body);
1280 //Roo.log(currentElement);
1282 var allText = false;
1283 var nodeName = currentElement.nodeName;
1284 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1286 if (nodeName == '#text') {
1287 return currentElement.nodeValue;
1292 if (nodeName != 'BODY') {
1295 // Prints the node tagName, such as <A>, <IMG>, etc
1298 for(i = 0; i < currentElement.attributes.length;i++) {
1300 var aname = currentElement.attributes.item(i).name;
1301 if (!currentElement.attributes.item(i).value.length) {
1304 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1307 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1316 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1319 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1324 // Traverse the tree
1326 var currentElementChild = currentElement.childNodes.item(i);
1330 while (currentElementChild) {
1331 // Formatting code (indent the tree so it looks nice on the screen)
1332 var nopad = nopadtext;
1333 if (lastnode == 'SPAN') {
1337 if (currentElementChild.nodeName == '#text') {
1338 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1339 if (!nopad && toadd.length > 80) {
1340 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1345 currentElementChild = currentElement.childNodes.item(i);
1351 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1353 // Recursively traverse the tree structure of the child node
1354 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1355 lastnode = currentElementChild.nodeName;
1357 currentElementChild=currentElement.childNodes.item(i);
1363 // The remaining code is mostly for formatting the tree
1364 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1369 ret+= "</"+tagName+">";
1375 // hide stuff that is not compatible
1393 * @cfg {String} fieldClass @hide
1396 * @cfg {String} focusClass @hide
1399 * @cfg {String} autoCreate @hide
1402 * @cfg {String} inputType @hide
1405 * @cfg {String} invalidClass @hide
1408 * @cfg {String} invalidText @hide
1411 * @cfg {String} msgFx @hide
1414 * @cfg {String} validateOnBlur @hide
1418 Roo.HtmlEditorCore.white = [
1419 'area', 'br', 'img', 'input', 'hr', 'wbr',
1421 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1422 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1423 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1424 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1425 'table', 'ul', 'xmp',
1427 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1430 'dir', 'menu', 'ol', 'ul', 'dl',
1436 Roo.HtmlEditorCore.black = [
1437 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1439 'base', 'basefont', 'bgsound', 'blink', 'body',
1440 'frame', 'frameset', 'head', 'html', 'ilayer',
1441 'iframe', 'layer', 'link', 'meta', 'object',
1442 'script', 'style' ,'title', 'xml' // clean later..
1444 Roo.HtmlEditorCore.clean = [
1445 'script', 'style', 'title', 'xml'
1447 Roo.HtmlEditorCore.remove = [
1452 Roo.HtmlEditorCore.ablack = [
1456 Roo.HtmlEditorCore.aclean = [
1457 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1461 Roo.HtmlEditorCore.pwhite= [
1462 'http', 'https', 'mailto'
1465 // white listed style attributes.
1466 Roo.HtmlEditorCore.cwhite= [
1467 // 'text-align', /// default is to allow most things..
1473 // black listed style attributes.
1474 Roo.HtmlEditorCore.cblack= [
1475 // 'font-size' -- this can be set by the project
1479 Roo.HtmlEditorCore.swapCodes =[