1 //<script type="text/javascript">
5 * Copyright(c) 2006-2007, Ext JS, LLC.
8 * http://www.extjs.com/license
14 * Default CSS appears to render it as fixed text by default (should really be Sans-Serif)
15 * - IE ? - no idea how much works there.
23 * @class Ext.form.HtmlEditor
24 * @extends Ext.form.Field
25 * Provides a lightweight HTML Editor component.
27 * This has been tested on Fireforx / Chrome.. IE may not be so great..
29 * <br><br><b>Note: The focus/blur and validation marking functionality inherited from Ext.form.Field is NOT
30 * supported by this editor.</b><br/><br/>
31 * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within
32 * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
34 Roo.form.HtmlEditor = Roo.extend(Roo.form.Field, {
36 * @cfg {Array} toolbars Array of toolbars. - defaults to just the Standard one
40 * @cfg {String} createLinkText The default text for the create link prompt
42 createLinkText : 'Please enter the URL for the link:',
44 * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
46 defaultLinkValue : 'http:/'+'/',
49 * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
54 * @cfg {Number} height (in pixels)
58 * @cfg {Number} width (in pixels)
63 * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
72 validationEvent : false,
76 sourceEditMode : false,
77 onFocus : Roo.emptyFn,
81 defaultAutoCreate : { // modified by initCompnoent..
83 style:"width:500px;height:300px;",
88 initComponent : function(){
92 * Fires when the editor is fully initialized (including the iframe)
93 * @param {HtmlEditor} this
98 * Fires when the editor is first receives the focus. Any insertion must wait
99 * until after this event.
100 * @param {HtmlEditor} this
105 * Fires before the textarea is updated with content from the editor iframe. Return false
106 * to cancel the sync.
107 * @param {HtmlEditor} this
108 * @param {String} html
113 * Fires before the iframe editor is updated with content from the textarea. Return false
114 * to cancel the push.
115 * @param {HtmlEditor} this
116 * @param {String} html
121 * Fires when the textarea is updated with content from the editor iframe.
122 * @param {HtmlEditor} this
123 * @param {String} html
128 * Fires when the iframe editor is updated with content from the textarea.
129 * @param {HtmlEditor} this
130 * @param {String} html
134 * @event editmodechange
135 * Fires when the editor switches edit modes
136 * @param {HtmlEditor} this
137 * @param {Boolean} sourceEdit True if source edit, false if standard editing.
139 editmodechange: true,
142 * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
143 * @param {HtmlEditor} this
147 this.defaultAutoCreate = {
149 style:'width: ' + this.width + 'px;height: ' + this.height + 'px;',
155 * Protected method that will not generally be called directly. It
156 * is called when the editor creates its toolbar. Override this method if you need to
157 * add custom toolbar buttons.
158 * @param {HtmlEditor} editor
160 createToolbar : function(editor){
161 if (!editor.toolbars || !editor.toolbars.length) {
162 editor.toolbars = [ new Roo.form.HtmlEditor.ToolbarStandard() ]; // can be empty?
165 for (var i =0 ; i < editor.toolbars.length;i++) {
166 editor.toolbars[i] = Roo.factory(editor.toolbars[i], Roo.form.HtmlEditor);
167 editor.toolbars[i].init(editor);
174 * Protected method that will not generally be called directly. It
175 * is called when the editor initializes the iframe with HTML contents. Override this method if you
176 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
178 getDocMarkup : function(){
181 if (this.stylesheets === false) {
183 Roo.get(document.head).select('style').each(function(node) {
184 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
187 Roo.get(document.head).select('link').each(function(node) {
188 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
191 } else if (!this.stylesheets.length) {
193 st = '<style type="text/css">' +
194 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
197 Roo.each(this.stylesheets, function(s) {
198 st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
203 return '<html><head>' + st +
204 //<style type="text/css">' +
205 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
207 ' </head><body></body></html>';
211 onRender : function(ct, position)
214 Roo.form.HtmlEditor.superclass.onRender.call(this, ct, position);
215 this.el.dom.style.border = '0 none';
216 this.el.dom.setAttribute('tabIndex', -1);
217 this.el.addClass('x-hidden');
218 if(Roo.isIE){ // fix IE 1px bogus margin
219 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
221 this.wrap = this.el.wrap({
222 cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
225 if (this.resizable) {
226 this.resizeEl = new Roo.Resizable(this.wrap, {
230 minHeight : this.height,
232 handles : this.resizable,
235 resize : function(r, w, h) {
236 _t.onResize(w,h); // -something
243 this.frameId = Roo.id();
245 this.createToolbar(this);
249 var iframe = this.wrap.createChild({
254 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
258 // console.log(iframe);
259 //this.wrap.dom.appendChild(iframe);
261 this.iframe = iframe.dom;
265 this.doc.designMode = 'on';
268 this.doc.write(this.getDocMarkup());
272 var task = { // must defer to wait for browser to be ready
274 //console.log("run task?" + this.doc.readyState);
276 if(this.doc.body || this.doc.readyState == 'complete'){
278 this.doc.designMode="on";
282 Roo.TaskMgr.stop(task);
283 this.initEditor.defer(10, this);
290 Roo.TaskMgr.start(task);
293 this.setSize(this.wrap.getSize());
296 this.resizeEl.resizeTo.defer(100, this.resizeEl,[ this.width,this.height ] );
297 // should trigger onReize..
302 onResize : function(w, h)
304 //Roo.log('resize: ' +w + ',' + h );
305 Roo.form.HtmlEditor.superclass.onResize.apply(this, arguments);
306 if(this.el && this.iframe){
307 if(typeof w == 'number'){
308 var aw = w - this.wrap.getFrameWidth('lr');
309 this.el.setWidth(this.adjustWidth('textarea', aw));
310 this.iframe.style.width = aw + 'px';
312 if(typeof h == 'number'){
314 for (var i =0; i < this.toolbars.length;i++) {
315 // fixme - ask toolbars for heights?
316 tbh += this.toolbars[i].tb.el.getHeight();
317 if (this.toolbars[i].footer) {
318 tbh += this.toolbars[i].footer.el.getHeight();
325 var ah = h - this.wrap.getFrameWidth('tb') - tbh;// this.tb.el.getHeight();
326 ah -= 5; // knock a few pixes off for look..
327 this.el.setHeight(this.adjustWidth('textarea', ah));
328 this.iframe.style.height = ah + 'px';
330 (this.doc.body || this.doc.documentElement).style.height = (ah - (this.iframePad*2)) + 'px';
337 * Toggles the editor between standard and source edit mode.
338 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
340 toggleSourceEdit : function(sourceEditMode){
342 this.sourceEditMode = sourceEditMode === true;
344 if(this.sourceEditMode){
347 this.iframe.className = 'x-hidden';
348 this.el.removeClass('x-hidden');
349 this.el.dom.removeAttribute('tabIndex');
354 this.iframe.className = '';
355 this.el.addClass('x-hidden');
356 this.el.dom.setAttribute('tabIndex', -1);
359 this.setSize(this.wrap.getSize());
360 this.fireEvent('editmodechange', this, this.sourceEditMode);
363 // private used internally
364 createLink : function(){
365 var url = prompt(this.createLinkText, this.defaultLinkValue);
366 if(url && url != 'http:/'+'/'){
367 this.relayCmd('createlink', url);
371 // private (for BoxComponent)
372 adjustSize : Roo.BoxComponent.prototype.adjustSize,
374 // private (for BoxComponent)
375 getResizeEl : function(){
379 // private (for BoxComponent)
380 getPositionEl : function(){
385 initEvents : function(){
386 this.originalValue = this.getValue();
390 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
393 markInvalid : Roo.emptyFn,
395 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
398 clearInvalid : Roo.emptyFn,
400 setValue : function(v){
401 Roo.form.HtmlEditor.superclass.setValue.call(this, v);
406 * Protected method that will not generally be called directly. If you need/want
407 * custom HTML cleanup, this is the method you should override.
408 * @param {String} html The HTML to be cleaned
409 * return {String} The cleaned HTML
411 cleanHtml : function(html){
414 if(Roo.isSafari){ // strip safari nonsense
415 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
418 if(html == ' '){
425 * Protected method that will not generally be called directly. Syncs the contents
426 * of the editor iframe with the textarea.
428 syncValue : function(){
429 if(this.initialized){
430 var bd = (this.doc.body || this.doc.documentElement);
431 //this.cleanUpPaste(); -- this is done else where and causes havoc..
432 var html = bd.innerHTML;
434 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
435 var m = bs.match(/text-align:(.*?);/i);
437 html = '<div style="'+m[0]+'">' + html + '</div>';
440 html = this.cleanHtml(html);
441 // fix up the special chars..
442 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
443 return "&#"+b.charCodeAt()+";"
445 if(this.fireEvent('beforesync', this, html) !== false){
446 this.el.dom.value = html;
447 this.fireEvent('sync', this, html);
453 * Protected method that will not generally be called directly. Pushes the value of the textarea
454 * into the iframe editor.
456 pushValue : function(){
457 if(this.initialized){
458 var v = this.el.dom.value;
463 if(this.fireEvent('beforepush', this, v) !== false){
464 var d = (this.doc.body || this.doc.documentElement);
467 this.el.dom.value = d.innerHTML;
468 this.fireEvent('push', this, v);
474 deferFocus : function(){
475 this.focus.defer(10, this);
480 if(this.win && !this.sourceEditMode){
487 assignDocWin: function()
489 var iframe = this.iframe;
492 this.doc = iframe.contentWindow.document;
493 this.win = iframe.contentWindow;
495 if (!Roo.get(this.frameId)) {
498 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
499 this.win = Roo.get(this.frameId).dom.contentWindow;
504 initEditor : function(){
505 //console.log("INIT EDITOR");
510 this.doc.designMode="on";
512 this.doc.write(this.getDocMarkup());
515 var dbody = (this.doc.body || this.doc.documentElement);
516 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
517 // this copies styles from the containing element into thsi one..
518 // not sure why we need all of this..
519 var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
520 ss['background-attachment'] = 'fixed'; // w3c
521 dbody.bgProperties = 'fixed'; // ie
522 Roo.DomHelper.applyStyles(dbody, ss);
523 Roo.EventManager.on(this.doc, {
524 //'mousedown': this.onEditorEvent,
525 'mouseup': this.onEditorEvent,
526 'dblclick': this.onEditorEvent,
527 'click': this.onEditorEvent,
528 'keyup': this.onEditorEvent,
533 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
535 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
536 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
538 this.initialized = true;
540 this.fireEvent('initialize', this);
545 onDestroy : function(){
551 for (var i =0; i < this.toolbars.length;i++) {
552 // fixme - ask toolbars for heights?
553 this.toolbars[i].onDestroy();
556 this.wrap.dom.innerHTML = '';
562 onFirstFocus : function(){
567 this.activated = true;
568 for (var i =0; i < this.toolbars.length;i++) {
569 this.toolbars[i].onFirstFocus();
572 if(Roo.isGecko){ // prevent silly gecko errors
574 var s = this.win.getSelection();
575 if(!s.focusNode || s.focusNode.nodeType != 3){
576 var r = s.getRangeAt(0);
577 r.selectNodeContents((this.doc.body || this.doc.documentElement));
582 this.execCmd('useCSS', true);
583 this.execCmd('styleWithCSS', false);
586 this.fireEvent('activate', this);
590 adjustFont: function(btn){
591 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
592 //if(Roo.isSafari){ // safari
595 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
596 if(Roo.isSafari){ // safari
597 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
598 v = (v < 10) ? 10 : v;
599 v = (v > 48) ? 48 : v;
600 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
605 v = Math.max(1, v+adjust);
607 this.execCmd('FontSize', v );
610 onEditorEvent : function(e){
611 this.fireEvent('editorevent', this, e);
612 // this.updateToolbar();
613 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
616 insertTag : function(tg)
618 // could be a bit smarter... -> wrap the current selected tRoo..
620 this.execCmd("formatblock", tg);
624 insertText : function(txt)
628 range = this.createRange();
629 range.deleteContents();
630 //alert(Sender.getAttribute('label'));
632 range.insertNode(this.doc.createTextNode(txt));
636 relayBtnCmd : function(btn){
637 this.relayCmd(btn.cmd);
641 * Executes a Midas editor command on the editor document and performs necessary focus and
642 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
643 * @param {String} cmd The Midas command
644 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
646 relayCmd : function(cmd, value){
648 this.execCmd(cmd, value);
649 this.fireEvent('editorevent', this);
650 //this.updateToolbar();
655 * Executes a Midas editor command directly on the editor document.
656 * For visual commands, you should use {@link #relayCmd} instead.
657 * <b>This should only be called after the editor is initialized.</b>
658 * @param {String} cmd The Midas command
659 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
661 execCmd : function(cmd, value){
662 this.doc.execCommand(cmd, false, value === undefined ? null : value);
673 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
675 * @param {String} text | dom node..
677 insertAtCursor : function(text)
688 var r = this.doc.selection.createRange();
699 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
703 // from jquery ui (MIT licenced)
707 if (win.getSelection && win.getSelection().getRangeAt) {
708 range = win.getSelection().getRangeAt(0);
709 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
710 range.insertNode(node);
711 } else if (win.document.selection && win.document.selection.createRange) {
712 // no firefox support
713 var txt = typeof(text) == 'string' ? text : text.outerHTML;
714 win.document.selection.createRange().pasteHTML(txt);
716 // no firefox support
717 var txt = typeof(text) == 'string' ? text : text.outerHTML;
718 this.execCmd('InsertHTML', txt);
727 mozKeyPress : function(e){
729 var c = e.getCharCode(), cmd;
732 c = String.fromCharCode(c).toLowerCase();
746 this.cleanUpPaste.defer(100, this);
762 fixKeys : function(){ // load time branching for fastest keydown performance
765 var k = e.getKey(), r;
768 r = this.doc.selection.createRange();
771 r.pasteHTML('    ');
778 r = this.doc.selection.createRange();
780 var target = r.parentElement();
781 if(!target || target.tagName.toLowerCase() != 'li'){
783 r.pasteHTML('<br />');
789 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
790 this.cleanUpPaste.defer(100, this);
796 }else if(Roo.isOpera){
802 this.execCmd('InsertHTML','    ');
805 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
806 this.cleanUpPaste.defer(100, this);
811 }else if(Roo.isSafari){
817 this.execCmd('InsertText','\t');
821 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
822 this.cleanUpPaste.defer(100, this);
830 getAllAncestors: function()
832 var p = this.getSelectedNode();
835 a.push(p); // push blank onto stack..
836 p = this.getParentElement();
840 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
844 a.push(this.doc.body);
851 getSelection : function()
854 return Roo.isIE ? this.doc.selection : this.win.getSelection();
857 getSelectedNode: function()
859 // this may only work on Gecko!!!
861 // should we cache this!!!!
866 var range = this.createRange(this.getSelection()).cloneRange();
869 var parent = range.parentElement();
871 var testRange = range.duplicate();
872 testRange.moveToElementText(parent);
873 if (testRange.inRange(range)) {
876 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
879 parent = parent.parentElement;
884 // is ancestor a text element.
885 var ac = range.commonAncestorContainer;
886 if (ac.nodeType == 3) {
890 var ar = ac.childNodes;
893 var other_nodes = [];
894 var has_other_nodes = false;
895 for (var i=0;i<ar.length;i++) {
896 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
899 // fullly contained node.
901 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
906 // probably selected..
907 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
908 other_nodes.push(ar[i]);
912 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
917 has_other_nodes = true;
919 if (!nodes.length && other_nodes.length) {
922 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
928 createRange: function(sel)
930 // this has strange effects when using with
931 // top toolbar - not sure if it's a great idea.
932 //this.editor.contentWindow.focus();
933 if (typeof sel != "undefined") {
935 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
937 return this.doc.createRange();
940 return this.doc.createRange();
943 getParentElement: function()
947 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
949 var range = this.createRange(sel);
952 var p = range.commonAncestorContainer;
953 while (p.nodeType == 3) { // text node
964 * Range intersection.. the hard stuff...
968 * [ -- selected range --- ]
972 * if end is before start or hits it. fail.
973 * if start is after end or hits it fail.
975 * if either hits (but other is outside. - then it's not
981 // @see http://www.thismuchiknow.co.uk/?p=64.
982 rangeIntersectsNode : function(range, node)
984 var nodeRange = node.ownerDocument.createRange();
986 nodeRange.selectNode(node);
988 nodeRange.selectNodeContents(node);
991 var rangeStartRange = range.cloneRange();
992 rangeStartRange.collapse(true);
994 var rangeEndRange = range.cloneRange();
995 rangeEndRange.collapse(false);
997 var nodeStartRange = nodeRange.cloneRange();
998 nodeStartRange.collapse(true);
1000 var nodeEndRange = nodeRange.cloneRange();
1001 nodeEndRange.collapse(false);
1003 return rangeStartRange.compareBoundaryPoints(
1004 Range.START_TO_START, nodeEndRange) == -1 &&
1005 rangeEndRange.compareBoundaryPoints(
1006 Range.START_TO_START, nodeStartRange) == 1;
1010 rangeCompareNode : function(range, node)
1012 var nodeRange = node.ownerDocument.createRange();
1014 nodeRange.selectNode(node);
1016 nodeRange.selectNodeContents(node);
1020 range.collapse(true);
1022 nodeRange.collapse(true);
1024 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1025 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
1027 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1029 var nodeIsBefore = ss == 1;
1030 var nodeIsAfter = ee == -1;
1032 if (nodeIsBefore && nodeIsAfter)
1034 if (!nodeIsBefore && nodeIsAfter)
1035 return 1; //right trailed.
1037 if (nodeIsBefore && !nodeIsAfter)
1038 return 2; // left trailed.
1043 // private? - in a new class?
1044 cleanUpPaste : function()
1046 // cleans up the whole document..
1047 Roo.log('cleanuppaste');
1048 this.cleanUpChildren(this.doc.body);
1049 var clean = this.cleanWordChars(this.doc.body.innerHTML);
1050 if (clean != this.doc.body.innerHTML) {
1051 this.doc.body.innerHTML = clean;
1056 cleanWordChars : function(input) {
1057 var he = Roo.form.HtmlEditor;
1060 Roo.each(he.swapCodes, function(sw) {
1062 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1063 output = output.replace(swapper, sw[1]);
1069 cleanUpChildren : function (n)
1071 if (!n.childNodes.length) {
1074 for (var i = n.childNodes.length-1; i > -1 ; i--) {
1075 this.cleanUpChild(n.childNodes[i]);
1082 cleanUpChild : function (node)
1084 //console.log(node);
1085 if (node.nodeName == "#text") {
1086 // clean up silly Windows -- stuff?
1089 if (node.nodeName == "#comment") {
1090 node.parentNode.removeChild(node);
1091 // clean up silly Windows -- stuff?
1095 if (Roo.form.HtmlEditor.black.indexOf(node.tagName.toLowerCase()) > -1) {
1097 node.parentNode.removeChild(node);
1102 var remove_keep_children= Roo.form.HtmlEditor.remove.indexOf(node.tagName.toLowerCase()) > -1;
1104 // remove <a name=....> as rendering on yahoo mailer is bored with this.
1106 if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1107 remove_keep_children = true;
1110 if (remove_keep_children) {
1111 this.cleanUpChildren(node);
1112 // inserts everything just before this node...
1113 while (node.childNodes.length) {
1114 var cn = node.childNodes[0];
1115 node.removeChild(cn);
1116 node.parentNode.insertBefore(cn, node);
1118 node.parentNode.removeChild(node);
1122 if (!node.attributes || !node.attributes.length) {
1123 this.cleanUpChildren(node);
1127 function cleanAttr(n,v)
1130 if (v.match(/^\./) || v.match(/^\//)) {
1133 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1136 Roo.log("(REMOVE)"+ node.tagName +'.' + n + '=' + v);
1137 node.removeAttribute(n);
1141 function cleanStyle(n,v)
1143 if (v.match(/expression/)) { //XSS?? should we even bother..
1144 node.removeAttribute(n);
1149 var parts = v.split(/;/);
1150 Roo.each(parts, function(p) {
1151 p = p.replace(/\s+/g,'');
1155 var l = p.split(':').shift().replace(/\s+/g,'');
1157 // only allow 'c whitelisted system attributes'
1158 if (Roo.form.HtmlEditor.cwhite.indexOf(l) < 0) {
1159 Roo.log('(REMOVE)' + node.tagName +'.' + n + ':'+l + '=' + v);
1160 node.removeAttribute(n);
1170 for (var i = node.attributes.length-1; i > -1 ; i--) {
1171 var a = node.attributes[i];
1173 if (Roo.form.HtmlEditor.ablack.indexOf(a.name.toLowerCase()) > -1) {
1174 node.removeAttribute(a.name);
1177 if (Roo.form.HtmlEditor.aclean.indexOf(a.name.toLowerCase()) > -1) {
1178 cleanAttr(a.name,a.value); // fixme..
1181 if (a.name == 'style') {
1182 cleanStyle(a.name,a.value);
1184 /// clean up MS crap..
1185 // tecnically this should be a list of valid class'es..
1188 if (a.name == 'class') {
1189 if (a.value.match(/^Mso/)) {
1190 node.className = '';
1193 if (a.value.match(/body/)) {
1194 node.className = '';
1204 this.cleanUpChildren(node);
1210 // hide stuff that is not compatible
1228 * @cfg {String} fieldClass @hide
1231 * @cfg {String} focusClass @hide
1234 * @cfg {String} autoCreate @hide
1237 * @cfg {String} inputType @hide
1240 * @cfg {String} invalidClass @hide
1243 * @cfg {String} invalidText @hide
1246 * @cfg {String} msgFx @hide
1249 * @cfg {String} validateOnBlur @hide
1253 Roo.form.HtmlEditor.white = [
1254 'area', 'br', 'img', 'input', 'hr', 'wbr',
1256 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1257 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1258 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1259 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1260 'table', 'ul', 'xmp',
1262 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1265 'dir', 'menu', 'ol', 'ul', 'dl',
1271 Roo.form.HtmlEditor.black = [
1272 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1274 'base', 'basefont', 'bgsound', 'blink', 'body',
1275 'frame', 'frameset', 'head', 'html', 'ilayer',
1276 'iframe', 'layer', 'link', 'meta', 'object',
1277 'script', 'style' ,'title', 'xml' // clean later..
1279 Roo.form.HtmlEditor.clean = [
1280 'script', 'style', 'title', 'xml'
1282 Roo.form.HtmlEditor.remove = [
1287 Roo.form.HtmlEditor.ablack = [
1291 Roo.form.HtmlEditor.aclean = [
1292 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1296 Roo.form.HtmlEditor.pwhite= [
1297 'http', 'https', 'mailto'
1300 // white listed style attributes.
1301 Roo.form.HtmlEditor.cwhite= [
1307 Roo.form.HtmlEditor.swapCodes =[