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.
26 * WARNING - THIS CURRENTlY ONLY WORKS ON FIREFOX - USE FCKeditor for a cross platform version
28 * <br><br><b>Note: The focus/blur and validation marking functionality inherited from Ext.form.Field is NOT
29 * supported by this editor.</b><br/><br/>
30 * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within
31 * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
33 Roo.form.HtmlEditor = Roo.extend(Roo.form.Field, {
35 * @cfg {Array} toolbars Array of toolbars. - defaults to just the Standard one
39 * @cfg {String} createLinkText The default text for the create link prompt
41 createLinkText : 'Please enter the URL for the link:',
43 * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
45 defaultLinkValue : 'http:/'+'/',
48 * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
53 * @cfg {Number} height (in pixels)
57 * @cfg {Number} width (in pixels)
62 * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
71 validationEvent : false,
75 sourceEditMode : false,
76 onFocus : Roo.emptyFn,
80 defaultAutoCreate : { // modified by initCompnoent..
82 style:"width:500px;height:300px;",
87 initComponent : function(){
91 * Fires when the editor is fully initialized (including the iframe)
92 * @param {HtmlEditor} this
97 * Fires when the editor is first receives the focus. Any insertion must wait
98 * until after this event.
99 * @param {HtmlEditor} this
104 * Fires before the textarea is updated with content from the editor iframe. Return false
105 * to cancel the sync.
106 * @param {HtmlEditor} this
107 * @param {String} html
112 * Fires before the iframe editor is updated with content from the textarea. Return false
113 * to cancel the push.
114 * @param {HtmlEditor} this
115 * @param {String} html
120 * Fires when the textarea is updated with content from the editor iframe.
121 * @param {HtmlEditor} this
122 * @param {String} html
127 * Fires when the iframe editor is updated with content from the textarea.
128 * @param {HtmlEditor} this
129 * @param {String} html
133 * @event editmodechange
134 * Fires when the editor switches edit modes
135 * @param {HtmlEditor} this
136 * @param {Boolean} sourceEdit True if source edit, false if standard editing.
138 editmodechange: true,
141 * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
142 * @param {HtmlEditor} this
146 this.defaultAutoCreate = {
148 style:'width: ' + this.width + 'px;height: ' + this.height + 'px;',
154 * Protected method that will not generally be called directly. It
155 * is called when the editor creates its toolbar. Override this method if you need to
156 * add custom toolbar buttons.
157 * @param {HtmlEditor} editor
159 createToolbar : function(editor){
160 if (!editor.toolbars || !editor.toolbars.length) {
161 editor.toolbars = [ new Roo.form.HtmlEditor.ToolbarStandard() ]; // can be empty?
164 for (var i =0 ; i < editor.toolbars.length;i++) {
165 editor.toolbars[i] = Roo.factory(editor.toolbars[i], Roo.form.HtmlEditor);
166 editor.toolbars[i].init(editor);
173 * Protected method that will not generally be called directly. It
174 * is called when the editor initializes the iframe with HTML contents. Override this method if you
175 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
177 getDocMarkup : function(){
180 if (this.stylesheets === false) {
182 Roo.get(document.head).select('style').each(function(node) {
183 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
186 Roo.get(document.head).select('link').each(function(node) {
187 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
190 } else if (!this.stylesheets.length) {
192 st = '<style type="text/css">' +
193 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
196 Roo.each(this.stylesheets, function(s) {
197 st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
202 return '<html><head>' + st +
203 //<style type="text/css">' +
204 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
206 ' </head><body></body></html>';
210 onRender : function(ct, position)
213 Roo.form.HtmlEditor.superclass.onRender.call(this, ct, position);
214 this.el.dom.style.border = '0 none';
215 this.el.dom.setAttribute('tabIndex', -1);
216 this.el.addClass('x-hidden');
217 if(Roo.isIE){ // fix IE 1px bogus margin
218 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
220 this.wrap = this.el.wrap({
221 cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
224 if (this.resizable) {
225 this.resizeEl = new Roo.Resizable(this.wrap, {
229 minHeight : this.height,
231 handles : this.resizable,
234 resize : function(r, w, h) {
235 _t.onResize(w,h); // -something
242 this.frameId = Roo.id();
244 this.createToolbar(this);
248 var iframe = this.wrap.createChild({
253 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
257 // console.log(iframe);
258 //this.wrap.dom.appendChild(iframe);
260 this.iframe = iframe.dom;
264 this.doc.designMode = 'on';
267 this.doc.write(this.getDocMarkup());
271 var task = { // must defer to wait for browser to be ready
273 //console.log("run task?" + this.doc.readyState);
275 if(this.doc.body || this.doc.readyState == 'complete'){
277 this.doc.designMode="on";
281 Roo.TaskMgr.stop(task);
282 this.initEditor.defer(10, this);
289 Roo.TaskMgr.start(task);
292 this.setSize(this.wrap.getSize());
295 this.resizeEl.resizeTo.defer(100, this.resizeEl,[ this.width,this.height ] );
296 // should trigger onReize..
301 onResize : function(w, h)
303 //Roo.log('resize: ' +w + ',' + h );
304 Roo.form.HtmlEditor.superclass.onResize.apply(this, arguments);
305 if(this.el && this.iframe){
306 if(typeof w == 'number'){
307 var aw = w - this.wrap.getFrameWidth('lr');
308 this.el.setWidth(this.adjustWidth('textarea', aw));
309 this.iframe.style.width = aw + 'px';
311 if(typeof h == 'number'){
313 for (var i =0; i < this.toolbars.length;i++) {
314 // fixme - ask toolbars for heights?
315 tbh += this.toolbars[i].tb.el.getHeight();
316 if (this.toolbars[i].footer) {
317 tbh += this.toolbars[i].footer.el.getHeight();
324 var ah = h - this.wrap.getFrameWidth('tb') - tbh;// this.tb.el.getHeight();
325 ah -= 5; // knock a few pixes off for look..
326 this.el.setHeight(this.adjustWidth('textarea', ah));
327 this.iframe.style.height = ah + 'px';
329 (this.doc.body || this.doc.documentElement).style.height = (ah - (this.iframePad*2)) + 'px';
336 * Toggles the editor between standard and source edit mode.
337 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
339 toggleSourceEdit : function(sourceEditMode){
341 this.sourceEditMode = sourceEditMode === true;
343 if(this.sourceEditMode){
346 this.iframe.className = 'x-hidden';
347 this.el.removeClass('x-hidden');
348 this.el.dom.removeAttribute('tabIndex');
353 this.iframe.className = '';
354 this.el.addClass('x-hidden');
355 this.el.dom.setAttribute('tabIndex', -1);
358 this.setSize(this.wrap.getSize());
359 this.fireEvent('editmodechange', this, this.sourceEditMode);
362 // private used internally
363 createLink : function(){
364 var url = prompt(this.createLinkText, this.defaultLinkValue);
365 if(url && url != 'http:/'+'/'){
366 this.relayCmd('createlink', url);
370 // private (for BoxComponent)
371 adjustSize : Roo.BoxComponent.prototype.adjustSize,
373 // private (for BoxComponent)
374 getResizeEl : function(){
378 // private (for BoxComponent)
379 getPositionEl : function(){
384 initEvents : function(){
385 this.originalValue = this.getValue();
389 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
392 markInvalid : Roo.emptyFn,
394 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
397 clearInvalid : Roo.emptyFn,
399 setValue : function(v){
400 Roo.form.HtmlEditor.superclass.setValue.call(this, v);
405 * Protected method that will not generally be called directly. If you need/want
406 * custom HTML cleanup, this is the method you should override.
407 * @param {String} html The HTML to be cleaned
408 * return {String} The cleaned HTML
410 cleanHtml : function(html){
413 if(Roo.isSafari){ // strip safari nonsense
414 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
417 if(html == ' '){
424 * Protected method that will not generally be called directly. Syncs the contents
425 * of the editor iframe with the textarea.
427 syncValue : function(){
428 if(this.initialized){
429 var bd = (this.doc.body || this.doc.documentElement);
430 //this.cleanUpPaste();
431 var html = bd.innerHTML;
433 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
434 var m = bs.match(/text-align:(.*?);/i);
436 html = '<div style="'+m[0]+'">' + html + '</div>';
439 html = this.cleanHtml(html);
440 // fix up the special chars..
441 html.replace(/([\x80-\uffff])/g, function (a, b) {
442 return "&#"+b.charCodeAt()+";"
444 if(this.fireEvent('beforesync', this, html) !== false){
445 this.el.dom.value = html;
446 this.fireEvent('sync', this, html);
452 * Protected method that will not generally be called directly. Pushes the value of the textarea
453 * into the iframe editor.
455 pushValue : function(){
456 if(this.initialized){
457 var v = this.el.dom.value;
462 if(this.fireEvent('beforepush', this, v) !== false){
463 var d = (this.doc.body || this.doc.documentElement);
466 this.el.dom.value = d.innerHTML;
467 this.fireEvent('push', this, v);
473 deferFocus : function(){
474 this.focus.defer(10, this);
479 if(this.win && !this.sourceEditMode){
486 assignDocWin: function()
488 var iframe = this.iframe;
491 this.doc = iframe.contentWindow.document;
492 this.win = iframe.contentWindow;
494 if (!Roo.get(this.frameId)) {
497 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
498 this.win = Roo.get(this.frameId).dom.contentWindow;
503 initEditor : function(){
504 //console.log("INIT EDITOR");
509 this.doc.designMode="on";
511 this.doc.write(this.getDocMarkup());
514 var dbody = (this.doc.body || this.doc.documentElement);
515 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
516 // this copies styles from the containing element into thsi one..
517 // not sure why we need all of this..
518 var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
519 ss['background-attachment'] = 'fixed'; // w3c
520 dbody.bgProperties = 'fixed'; // ie
521 Roo.DomHelper.applyStyles(dbody, ss);
522 Roo.EventManager.on(this.doc, {
523 //'mousedown': this.onEditorEvent,
524 'mouseup': this.onEditorEvent,
525 'dblclick': this.onEditorEvent,
526 'click': this.onEditorEvent,
527 'keyup': this.onEditorEvent,
532 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
534 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
535 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
537 this.initialized = true;
539 this.fireEvent('initialize', this);
544 onDestroy : function(){
550 for (var i =0; i < this.toolbars.length;i++) {
551 // fixme - ask toolbars for heights?
552 this.toolbars[i].onDestroy();
555 this.wrap.dom.innerHTML = '';
561 onFirstFocus : function(){
566 this.activated = true;
567 for (var i =0; i < this.toolbars.length;i++) {
568 this.toolbars[i].onFirstFocus();
571 if(Roo.isGecko){ // prevent silly gecko errors
573 var s = this.win.getSelection();
574 if(!s.focusNode || s.focusNode.nodeType != 3){
575 var r = s.getRangeAt(0);
576 r.selectNodeContents((this.doc.body || this.doc.documentElement));
581 this.execCmd('useCSS', true);
582 this.execCmd('styleWithCSS', false);
585 this.fireEvent('activate', this);
589 adjustFont: function(btn){
590 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
591 //if(Roo.isSafari){ // safari
594 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
595 if(Roo.isSafari){ // safari
596 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
597 v = (v < 10) ? 10 : v;
598 v = (v > 48) ? 48 : v;
599 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
604 v = Math.max(1, v+adjust);
606 this.execCmd('FontSize', v );
609 onEditorEvent : function(e){
610 this.fireEvent('editorevent', this, e);
611 // this.updateToolbar();
612 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
615 insertTag : function(tg)
617 // could be a bit smarter... -> wrap the current selected tRoo..
619 this.execCmd("formatblock", tg);
623 insertText : function(txt)
627 range = this.createRange();
628 range.deleteContents();
629 //alert(Sender.getAttribute('label'));
631 range.insertNode(this.doc.createTextNode(txt));
635 relayBtnCmd : function(btn){
636 this.relayCmd(btn.cmd);
640 * Executes a Midas editor command on the editor document and performs necessary focus and
641 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
642 * @param {String} cmd The Midas command
643 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
645 relayCmd : function(cmd, value){
647 this.execCmd(cmd, value);
648 this.fireEvent('editorevent', this);
649 //this.updateToolbar();
654 * Executes a Midas editor command directly on the editor document.
655 * For visual commands, you should use {@link #relayCmd} instead.
656 * <b>This should only be called after the editor is initialized.</b>
657 * @param {String} cmd The Midas command
658 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
660 execCmd : function(cmd, value){
661 this.doc.execCommand(cmd, false, value === undefined ? null : value);
667 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
669 * @param {String} text | dom node..
671 insertAtCursor : function(text)
682 var r = this.doc.selection.createRange();
693 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
697 // from jquery ui (MIT licenced)
701 if (win.getSelection && win.getSelection().getRangeAt) {
702 range = win.getSelection().getRangeAt(0);
703 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
704 range.insertNode(node);
705 } else if (win.document.selection && win.document.selection.createRange) {
706 // no firefox support
707 var txt = typeof(text) == 'string' ? text : text.outerHTML;
708 win.document.selection.createRange().pasteHTML(txt);
710 // no firefox support
711 var txt = typeof(text) == 'string' ? text : text.outerHTML;
712 this.execCmd('InsertHTML', txt);
721 mozKeyPress : function(e){
723 var c = e.getCharCode(), cmd;
726 c = String.fromCharCode(c).toLowerCase();
738 this.cleanUpPaste.defer(100, this);
754 fixKeys : function(){ // load time branching for fastest keydown performance
757 var k = e.getKey(), r;
760 r = this.doc.selection.createRange();
763 r.pasteHTML('    ');
770 r = this.doc.selection.createRange();
772 var target = r.parentElement();
773 if(!target || target.tagName.toLowerCase() != 'li'){
775 r.pasteHTML('<br />');
781 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
782 this.cleanUpPaste.defer(100, this);
788 }else if(Roo.isOpera){
794 this.execCmd('InsertHTML','    ');
797 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
798 this.cleanUpPaste.defer(100, this);
803 }else if(Roo.isSafari){
809 this.execCmd('InsertText','\t');
813 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
814 this.cleanUpPaste.defer(100, this);
822 getAllAncestors: function()
824 var p = this.getSelectedNode();
827 a.push(p); // push blank onto stack..
828 p = this.getParentElement();
832 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
836 a.push(this.doc.body);
843 getSelection : function()
846 return Roo.isIE ? this.doc.selection : this.win.getSelection();
849 getSelectedNode: function()
851 // this may only work on Gecko!!!
853 // should we cache this!!!!
858 var range = this.createRange(this.getSelection()).cloneRange();
861 var parent = range.parentElement();
863 var testRange = range.duplicate();
864 testRange.moveToElementText(parent);
865 if (testRange.inRange(range)) {
868 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
871 parent = parent.parentElement;
876 // is ancestor a text element.
877 var ac = range.commonAncestorContainer;
878 if (ac.nodeType == 3) {
882 var ar = ac.childNodes;
885 var other_nodes = [];
886 var has_other_nodes = false;
887 for (var i=0;i<ar.length;i++) {
888 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
891 // fullly contained node.
893 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
898 // probably selected..
899 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
900 other_nodes.push(ar[i]);
904 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
909 has_other_nodes = true;
911 if (!nodes.length && other_nodes.length) {
914 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
920 createRange: function(sel)
922 // this has strange effects when using with
923 // top toolbar - not sure if it's a great idea.
924 //this.editor.contentWindow.focus();
925 if (typeof sel != "undefined") {
927 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
929 return this.doc.createRange();
932 return this.doc.createRange();
935 getParentElement: function()
939 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
941 var range = this.createRange(sel);
944 var p = range.commonAncestorContainer;
945 while (p.nodeType == 3) { // text node
956 * Range intersection.. the hard stuff...
960 * [ -- selected range --- ]
964 * if end is before start or hits it. fail.
965 * if start is after end or hits it fail.
967 * if either hits (but other is outside. - then it's not
973 // @see http://www.thismuchiknow.co.uk/?p=64.
974 rangeIntersectsNode : function(range, node)
976 var nodeRange = node.ownerDocument.createRange();
978 nodeRange.selectNode(node);
980 nodeRange.selectNodeContents(node);
983 var rangeStartRange = range.cloneRange();
984 rangeStartRange.collapse(true);
986 var rangeEndRange = range.cloneRange();
987 rangeEndRange.collapse(false);
989 var nodeStartRange = nodeRange.cloneRange();
990 nodeStartRange.collapse(true);
992 var nodeEndRange = nodeRange.cloneRange();
993 nodeEndRange.collapse(false);
995 return rangeStartRange.compareBoundaryPoints(
996 Range.START_TO_START, nodeEndRange) == -1 &&
997 rangeEndRange.compareBoundaryPoints(
998 Range.START_TO_START, nodeStartRange) == 1;
1002 rangeCompareNode : function(range, node)
1004 var nodeRange = node.ownerDocument.createRange();
1006 nodeRange.selectNode(node);
1008 nodeRange.selectNodeContents(node);
1012 range.collapse(true);
1014 nodeRange.collapse(true);
1016 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1017 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
1019 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1021 var nodeIsBefore = ss == 1;
1022 var nodeIsAfter = ee == -1;
1024 if (nodeIsBefore && nodeIsAfter)
1026 if (!nodeIsBefore && nodeIsAfter)
1027 return 1; //right trailed.
1029 if (nodeIsBefore && !nodeIsAfter)
1030 return 2; // left trailed.
1035 // private? - in a new class?
1036 cleanUpPaste : function()
1038 // cleans up the whole document..
1039 Roo.log('cleanuppaste');
1040 this.cleanUpChildren(this.doc.body);
1041 var clean = this.cleanWordChars(this.doc.body.innerHTML);
1042 if (clean != this.doc.body.innerHTML) {
1043 this.doc.body.innerHTML = clean;
1048 cleanWordChars : function(input) {
1049 var he = Roo.form.HtmlEditor;
1052 Roo.each(he.swapCodes, function(sw) {
1054 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1055 output = output.replace(swapper, sw[1]);
1061 cleanUpChildren : function (n)
1063 if (!n.childNodes.length) {
1066 for (var i = n.childNodes.length-1; i > -1 ; i--) {
1067 this.cleanUpChild(n.childNodes[i]);
1074 cleanUpChild : function (node)
1076 //console.log(node);
1077 if (node.nodeName == "#text") {
1078 // clean up silly Windows -- stuff?
1081 if (node.nodeName == "#comment") {
1082 node.parentNode.removeChild(node);
1083 // clean up silly Windows -- stuff?
1087 if (Roo.form.HtmlEditor.black.indexOf(node.tagName.toLowerCase()) > -1) {
1089 node.parentNode.removeChild(node);
1094 var remove_keep_children= Roo.form.HtmlEditor.remove.indexOf(node.tagName.toLowerCase()) > -1;
1096 // remove <a name=....> as rendering on yahoo mailer is bored with this.
1098 if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1099 remove_keep_children = true;
1102 if (remove_keep_children) {
1103 this.cleanUpChildren(node);
1104 // inserts everything just before this node...
1105 while (node.childNodes.length) {
1106 var cn = node.childNodes[0];
1107 node.removeChild(cn);
1108 node.parentNode.insertBefore(cn, node);
1110 node.parentNode.removeChild(node);
1114 if (!node.attributes || !node.attributes.length) {
1115 this.cleanUpChildren(node);
1119 function cleanAttr(n,v)
1122 if (v.match(/^\./) || v.match(/^\//)) {
1125 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1128 Roo.log("(REMOVE)"+ node.tagName +'.' + n + '=' + v);
1129 node.removeAttribute(n);
1133 function cleanStyle(n,v)
1135 if (v.match(/expression/)) { //XSS?? should we even bother..
1136 node.removeAttribute(n);
1141 var parts = v.split(/;/);
1142 Roo.each(parts, function(p) {
1143 p = p.replace(/\s+/g,'');
1147 var l = p.split(':').shift().replace(/\s+/g,'');
1149 // only allow 'c whitelisted system attributes'
1150 if (Roo.form.HtmlEditor.cwhite.indexOf(l) < 0) {
1151 Roo.log('(REMOVE)' + node.tagName +'.' + n + ':'+l + '=' + v);
1152 node.removeAttribute(n);
1162 for (var i = node.attributes.length-1; i > -1 ; i--) {
1163 var a = node.attributes[i];
1165 if (Roo.form.HtmlEditor.ablack.indexOf(a.name.toLowerCase()) > -1) {
1166 node.removeAttribute(a.name);
1169 if (Roo.form.HtmlEditor.aclean.indexOf(a.name.toLowerCase()) > -1) {
1170 cleanAttr(a.name,a.value); // fixme..
1173 if (a.name == 'style') {
1174 cleanStyle(a.name,a.value);
1176 /// clean up MS crap..
1177 // tecnically this should be a list of valid class'es..
1180 if (a.name == 'class') {
1181 if (a.value.match(/^Mso/)) {
1182 node.className = '';
1185 if (a.value.match(/body/)) {
1186 node.className = '';
1196 this.cleanUpChildren(node);
1202 // hide stuff that is not compatible
1220 * @cfg {String} fieldClass @hide
1223 * @cfg {String} focusClass @hide
1226 * @cfg {String} autoCreate @hide
1229 * @cfg {String} inputType @hide
1232 * @cfg {String} invalidClass @hide
1235 * @cfg {String} invalidText @hide
1238 * @cfg {String} msgFx @hide
1241 * @cfg {String} validateOnBlur @hide
1245 Roo.form.HtmlEditor.white = [
1246 'area', 'br', 'img', 'input', 'hr', 'wbr',
1248 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1249 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1250 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1251 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1252 'table', 'ul', 'xmp',
1254 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1257 'dir', 'menu', 'ol', 'ul', 'dl',
1263 Roo.form.HtmlEditor.black = [
1264 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1266 'base', 'basefont', 'bgsound', 'blink', 'body',
1267 'frame', 'frameset', 'head', 'html', 'ilayer',
1268 'iframe', 'layer', 'link', 'meta', 'object',
1269 'script', 'style' ,'title', 'xml' // clean later..
1271 Roo.form.HtmlEditor.clean = [
1272 'script', 'style', 'title', 'xml'
1274 Roo.form.HtmlEditor.remove = [
1279 Roo.form.HtmlEditor.ablack = [
1283 Roo.form.HtmlEditor.aclean = [
1284 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1288 Roo.form.HtmlEditor.pwhite= [
1289 'http', 'https', 'mailto'
1292 // white listed style attributes.
1293 Roo.form.HtmlEditor.cwhite= [
1299 Roo.form.HtmlEditor.swapCodes =[