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 st += '<style type="text/css">' +
204 'IMG { cursor: pointer } ' +
208 return '<html><head>' + st +
209 //<style type="text/css">' +
210 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
212 ' </head><body class="roo-htmleditor-body"></body></html>';
216 onRender : function(ct, position)
219 Roo.form.HtmlEditor.superclass.onRender.call(this, ct, position);
220 this.el.dom.style.border = '0 none';
221 this.el.dom.setAttribute('tabIndex', -1);
222 this.el.addClass('x-hidden');
223 if(Roo.isIE){ // fix IE 1px bogus margin
224 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
226 this.wrap = this.el.wrap({
227 cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
230 if (this.resizable) {
231 this.resizeEl = new Roo.Resizable(this.wrap, {
235 minHeight : this.height,
237 handles : this.resizable,
240 resize : function(r, w, h) {
241 _t.onResize(w,h); // -something
248 this.frameId = Roo.id();
250 this.createToolbar(this);
254 var iframe = this.wrap.createChild({
259 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
263 // console.log(iframe);
264 //this.wrap.dom.appendChild(iframe);
266 this.iframe = iframe.dom;
270 this.doc.designMode = 'on';
273 this.doc.write(this.getDocMarkup());
277 var task = { // must defer to wait for browser to be ready
279 //console.log("run task?" + this.doc.readyState);
281 if(this.doc.body || this.doc.readyState == 'complete'){
283 this.doc.designMode="on";
287 Roo.TaskMgr.stop(task);
288 this.initEditor.defer(10, this);
295 Roo.TaskMgr.start(task);
298 this.setSize(this.wrap.getSize());
301 this.resizeEl.resizeTo.defer(100, this.resizeEl,[ this.width,this.height ] );
302 // should trigger onReize..
307 onResize : function(w, h)
309 //Roo.log('resize: ' +w + ',' + h );
310 Roo.form.HtmlEditor.superclass.onResize.apply(this, arguments);
311 if(this.el && this.iframe){
312 if(typeof w == 'number'){
313 var aw = w - this.wrap.getFrameWidth('lr');
314 this.el.setWidth(this.adjustWidth('textarea', aw));
315 this.iframe.style.width = aw + 'px';
317 if(typeof h == 'number'){
319 for (var i =0; i < this.toolbars.length;i++) {
320 // fixme - ask toolbars for heights?
321 tbh += this.toolbars[i].tb.el.getHeight();
322 if (this.toolbars[i].footer) {
323 tbh += this.toolbars[i].footer.el.getHeight();
330 var ah = h - this.wrap.getFrameWidth('tb') - tbh;// this.tb.el.getHeight();
331 ah -= 5; // knock a few pixes off for look..
332 this.el.setHeight(this.adjustWidth('textarea', ah));
333 this.iframe.style.height = ah + 'px';
335 (this.doc.body || this.doc.documentElement).style.height = (ah - (this.iframePad*2)) + 'px';
342 * Toggles the editor between standard and source edit mode.
343 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
345 toggleSourceEdit : function(sourceEditMode){
347 this.sourceEditMode = sourceEditMode === true;
349 if(this.sourceEditMode){
352 this.iframe.className = 'x-hidden';
353 this.el.removeClass('x-hidden');
354 this.el.dom.removeAttribute('tabIndex');
359 this.iframe.className = '';
360 this.el.addClass('x-hidden');
361 this.el.dom.setAttribute('tabIndex', -1);
364 this.setSize(this.wrap.getSize());
365 this.fireEvent('editmodechange', this, this.sourceEditMode);
368 // private used internally
369 createLink : function(){
370 var url = prompt(this.createLinkText, this.defaultLinkValue);
371 if(url && url != 'http:/'+'/'){
372 this.relayCmd('createlink', url);
376 // private (for BoxComponent)
377 adjustSize : Roo.BoxComponent.prototype.adjustSize,
379 // private (for BoxComponent)
380 getResizeEl : function(){
384 // private (for BoxComponent)
385 getPositionEl : function(){
390 initEvents : function(){
391 this.originalValue = this.getValue();
395 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
398 markInvalid : Roo.emptyFn,
400 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
403 clearInvalid : Roo.emptyFn,
405 setValue : function(v){
406 Roo.form.HtmlEditor.superclass.setValue.call(this, v);
411 * Protected method that will not generally be called directly. If you need/want
412 * custom HTML cleanup, this is the method you should override.
413 * @param {String} html The HTML to be cleaned
414 * return {String} The cleaned HTML
416 cleanHtml : function(html){
419 if(Roo.isSafari){ // strip safari nonsense
420 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
423 if(html == ' '){
430 * Protected method that will not generally be called directly. Syncs the contents
431 * of the editor iframe with the textarea.
433 syncValue : function(){
434 if(this.initialized){
435 var bd = (this.doc.body || this.doc.documentElement);
436 //this.cleanUpPaste(); -- this is done else where and causes havoc..
437 var html = bd.innerHTML;
439 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
440 var m = bs.match(/text-align:(.*?);/i);
442 html = '<div style="'+m[0]+'">' + html + '</div>';
445 html = this.cleanHtml(html);
446 // fix up the special chars..
447 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
448 return "&#"+b.charCodeAt()+";"
450 if(this.fireEvent('beforesync', this, html) !== false){
451 this.el.dom.value = html;
452 this.fireEvent('sync', this, html);
458 * Protected method that will not generally be called directly. Pushes the value of the textarea
459 * into the iframe editor.
461 pushValue : function(){
462 if(this.initialized){
463 var v = this.el.dom.value;
468 if(this.fireEvent('beforepush', this, v) !== false){
469 var d = (this.doc.body || this.doc.documentElement);
472 this.el.dom.value = d.innerHTML;
473 this.fireEvent('push', this, v);
479 deferFocus : function(){
480 this.focus.defer(10, this);
485 if(this.win && !this.sourceEditMode){
492 assignDocWin: function()
494 var iframe = this.iframe;
497 this.doc = iframe.contentWindow.document;
498 this.win = iframe.contentWindow;
500 if (!Roo.get(this.frameId)) {
503 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
504 this.win = Roo.get(this.frameId).dom.contentWindow;
509 initEditor : function(){
510 //console.log("INIT EDITOR");
515 this.doc.designMode="on";
517 this.doc.write(this.getDocMarkup());
520 var dbody = (this.doc.body || this.doc.documentElement);
521 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
522 // this copies styles from the containing element into thsi one..
523 // not sure why we need all of this..
524 var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
525 ss['background-attachment'] = 'fixed'; // w3c
526 dbody.bgProperties = 'fixed'; // ie
527 Roo.DomHelper.applyStyles(dbody, ss);
528 Roo.EventManager.on(this.doc, {
529 //'mousedown': this.onEditorEvent,
530 'mouseup': this.onEditorEvent,
531 'dblclick': this.onEditorEvent,
532 'click': this.onEditorEvent,
533 'keyup': this.onEditorEvent,
538 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
540 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
541 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
543 this.initialized = true;
545 this.fireEvent('initialize', this);
550 onDestroy : function(){
556 for (var i =0; i < this.toolbars.length;i++) {
557 // fixme - ask toolbars for heights?
558 this.toolbars[i].onDestroy();
561 this.wrap.dom.innerHTML = '';
567 onFirstFocus : function(){
572 this.activated = true;
573 for (var i =0; i < this.toolbars.length;i++) {
574 this.toolbars[i].onFirstFocus();
577 if(Roo.isGecko){ // prevent silly gecko errors
579 var s = this.win.getSelection();
580 if(!s.focusNode || s.focusNode.nodeType != 3){
581 var r = s.getRangeAt(0);
582 r.selectNodeContents((this.doc.body || this.doc.documentElement));
587 this.execCmd('useCSS', true);
588 this.execCmd('styleWithCSS', false);
591 this.fireEvent('activate', this);
595 adjustFont: function(btn){
596 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
597 //if(Roo.isSafari){ // safari
600 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
601 if(Roo.isSafari){ // safari
602 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
603 v = (v < 10) ? 10 : v;
604 v = (v > 48) ? 48 : v;
605 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
610 v = Math.max(1, v+adjust);
612 this.execCmd('FontSize', v );
615 onEditorEvent : function(e){
616 this.fireEvent('editorevent', this, e);
617 // this.updateToolbar();
618 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
621 insertTag : function(tg)
623 // could be a bit smarter... -> wrap the current selected tRoo..
625 this.execCmd("formatblock", tg);
629 insertText : function(txt)
633 range = this.createRange();
634 range.deleteContents();
635 //alert(Sender.getAttribute('label'));
637 range.insertNode(this.doc.createTextNode(txt));
641 relayBtnCmd : function(btn){
642 this.relayCmd(btn.cmd);
646 * Executes a Midas editor command on the editor document and performs necessary focus and
647 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
648 * @param {String} cmd The Midas command
649 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
651 relayCmd : function(cmd, value){
653 this.execCmd(cmd, value);
654 this.fireEvent('editorevent', this);
655 //this.updateToolbar();
660 * Executes a Midas editor command directly on the editor document.
661 * For visual commands, you should use {@link #relayCmd} instead.
662 * <b>This should only be called after the editor is initialized.</b>
663 * @param {String} cmd The Midas command
664 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
666 execCmd : function(cmd, value){
667 this.doc.execCommand(cmd, false, value === undefined ? null : value);
674 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
676 * @param {String} text | dom node..
678 insertAtCursor : function(text)
689 var r = this.doc.selection.createRange();
700 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
704 // from jquery ui (MIT licenced)
708 if (win.getSelection && win.getSelection().getRangeAt) {
709 range = win.getSelection().getRangeAt(0);
710 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
711 range.insertNode(node);
712 } else if (win.document.selection && win.document.selection.createRange) {
713 // no firefox support
714 var txt = typeof(text) == 'string' ? text : text.outerHTML;
715 win.document.selection.createRange().pasteHTML(txt);
717 // no firefox support
718 var txt = typeof(text) == 'string' ? text : text.outerHTML;
719 this.execCmd('InsertHTML', txt);
728 mozKeyPress : function(e){
730 var c = e.getCharCode(), cmd;
733 c = String.fromCharCode(c).toLowerCase();
747 this.cleanUpPaste.defer(100, this);
763 fixKeys : function(){ // load time branching for fastest keydown performance
766 var k = e.getKey(), r;
769 r = this.doc.selection.createRange();
772 r.pasteHTML('    ');
779 r = this.doc.selection.createRange();
781 var target = r.parentElement();
782 if(!target || target.tagName.toLowerCase() != 'li'){
784 r.pasteHTML('<br />');
790 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
791 this.cleanUpPaste.defer(100, this);
797 }else if(Roo.isOpera){
803 this.execCmd('InsertHTML','    ');
806 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
807 this.cleanUpPaste.defer(100, this);
812 }else if(Roo.isSafari){
818 this.execCmd('InsertText','\t');
822 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
823 this.cleanUpPaste.defer(100, this);
831 getAllAncestors: function()
833 var p = this.getSelectedNode();
836 a.push(p); // push blank onto stack..
837 p = this.getParentElement();
841 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
845 a.push(this.doc.body);
852 getSelection : function()
855 return Roo.isIE ? this.doc.selection : this.win.getSelection();
858 getSelectedNode: function()
860 // this may only work on Gecko!!!
862 // should we cache this!!!!
867 var range = this.createRange(this.getSelection()).cloneRange();
870 var parent = range.parentElement();
872 var testRange = range.duplicate();
873 testRange.moveToElementText(parent);
874 if (testRange.inRange(range)) {
877 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
880 parent = parent.parentElement;
885 // is ancestor a text element.
886 var ac = range.commonAncestorContainer;
887 if (ac.nodeType == 3) {
891 var ar = ac.childNodes;
894 var other_nodes = [];
895 var has_other_nodes = false;
896 for (var i=0;i<ar.length;i++) {
897 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
900 // fullly contained node.
902 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
907 // probably selected..
908 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
909 other_nodes.push(ar[i]);
913 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
918 has_other_nodes = true;
920 if (!nodes.length && other_nodes.length) {
923 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
929 createRange: function(sel)
931 // this has strange effects when using with
932 // top toolbar - not sure if it's a great idea.
933 //this.editor.contentWindow.focus();
934 if (typeof sel != "undefined") {
936 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
938 return this.doc.createRange();
941 return this.doc.createRange();
944 getParentElement: function()
948 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
950 var range = this.createRange(sel);
953 var p = range.commonAncestorContainer;
954 while (p.nodeType == 3) { // text node
965 * Range intersection.. the hard stuff...
969 * [ -- selected range --- ]
973 * if end is before start or hits it. fail.
974 * if start is after end or hits it fail.
976 * if either hits (but other is outside. - then it's not
982 // @see http://www.thismuchiknow.co.uk/?p=64.
983 rangeIntersectsNode : function(range, node)
985 var nodeRange = node.ownerDocument.createRange();
987 nodeRange.selectNode(node);
989 nodeRange.selectNodeContents(node);
992 var rangeStartRange = range.cloneRange();
993 rangeStartRange.collapse(true);
995 var rangeEndRange = range.cloneRange();
996 rangeEndRange.collapse(false);
998 var nodeStartRange = nodeRange.cloneRange();
999 nodeStartRange.collapse(true);
1001 var nodeEndRange = nodeRange.cloneRange();
1002 nodeEndRange.collapse(false);
1004 return rangeStartRange.compareBoundaryPoints(
1005 Range.START_TO_START, nodeEndRange) == -1 &&
1006 rangeEndRange.compareBoundaryPoints(
1007 Range.START_TO_START, nodeStartRange) == 1;
1011 rangeCompareNode : function(range, node)
1013 var nodeRange = node.ownerDocument.createRange();
1015 nodeRange.selectNode(node);
1017 nodeRange.selectNodeContents(node);
1021 range.collapse(true);
1023 nodeRange.collapse(true);
1025 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1026 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
1028 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1030 var nodeIsBefore = ss == 1;
1031 var nodeIsAfter = ee == -1;
1033 if (nodeIsBefore && nodeIsAfter)
1035 if (!nodeIsBefore && nodeIsAfter)
1036 return 1; //right trailed.
1038 if (nodeIsBefore && !nodeIsAfter)
1039 return 2; // left trailed.
1044 // private? - in a new class?
1045 cleanUpPaste : function()
1047 // cleans up the whole document..
1048 Roo.log('cleanuppaste');
1049 this.cleanUpChildren(this.doc.body);
1050 var clean = this.cleanWordChars(this.doc.body.innerHTML);
1051 if (clean != this.doc.body.innerHTML) {
1052 this.doc.body.innerHTML = clean;
1057 cleanWordChars : function(input) {
1058 var he = Roo.form.HtmlEditor;
1061 Roo.each(he.swapCodes, function(sw) {
1063 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1064 output = output.replace(swapper, sw[1]);
1070 cleanUpChildren : function (n)
1072 if (!n.childNodes.length) {
1075 for (var i = n.childNodes.length-1; i > -1 ; i--) {
1076 this.cleanUpChild(n.childNodes[i]);
1083 cleanUpChild : function (node)
1085 //console.log(node);
1086 if (node.nodeName == "#text") {
1087 // clean up silly Windows -- stuff?
1090 if (node.nodeName == "#comment") {
1091 node.parentNode.removeChild(node);
1092 // clean up silly Windows -- stuff?
1096 if (Roo.form.HtmlEditor.black.indexOf(node.tagName.toLowerCase()) > -1) {
1098 node.parentNode.removeChild(node);
1103 var remove_keep_children= Roo.form.HtmlEditor.remove.indexOf(node.tagName.toLowerCase()) > -1;
1105 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1106 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1108 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1109 // remove_keep_children = true;
1112 if (remove_keep_children) {
1113 this.cleanUpChildren(node);
1114 // inserts everything just before this node...
1115 while (node.childNodes.length) {
1116 var cn = node.childNodes[0];
1117 node.removeChild(cn);
1118 node.parentNode.insertBefore(cn, node);
1120 node.parentNode.removeChild(node);
1124 if (!node.attributes || !node.attributes.length) {
1125 this.cleanUpChildren(node);
1129 function cleanAttr(n,v)
1132 if (v.match(/^\./) || v.match(/^\//)) {
1135 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1138 if (v.match(/^#/)) {
1141 Roo.log("(REMOVE)"+ node.tagName +'.' + n + '=' + v);
1142 node.removeAttribute(n);
1146 function cleanStyle(n,v)
1148 if (v.match(/expression/)) { //XSS?? should we even bother..
1149 node.removeAttribute(n);
1154 var parts = v.split(/;/);
1155 Roo.each(parts, function(p) {
1156 p = p.replace(/\s+/g,'');
1160 var l = p.split(':').shift().replace(/\s+/g,'');
1162 // only allow 'c whitelisted system attributes'
1163 if (Roo.form.HtmlEditor.cwhite.indexOf(l) < 0) {
1164 Roo.log('(REMOVE)' + node.tagName +'.' + n + ':'+l + '=' + v);
1165 node.removeAttribute(n);
1175 for (var i = node.attributes.length-1; i > -1 ; i--) {
1176 var a = node.attributes[i];
1178 if (Roo.form.HtmlEditor.ablack.indexOf(a.name.toLowerCase()) > -1) {
1179 node.removeAttribute(a.name);
1182 if (Roo.form.HtmlEditor.aclean.indexOf(a.name.toLowerCase()) > -1) {
1183 cleanAttr(a.name,a.value); // fixme..
1186 if (a.name == 'style') {
1187 cleanStyle(a.name,a.value);
1190 /// clean up MS crap..
1191 // tecnically this should be a list of valid class'es..
1194 if (a.name == 'class') {
1195 if (a.value.match(/^Mso/)) {
1196 node.className = '';
1199 if (a.value.match(/body/)) {
1200 node.className = '';
1211 this.cleanUpChildren(node);
1217 // hide stuff that is not compatible
1235 * @cfg {String} fieldClass @hide
1238 * @cfg {String} focusClass @hide
1241 * @cfg {String} autoCreate @hide
1244 * @cfg {String} inputType @hide
1247 * @cfg {String} invalidClass @hide
1250 * @cfg {String} invalidText @hide
1253 * @cfg {String} msgFx @hide
1256 * @cfg {String} validateOnBlur @hide
1260 Roo.form.HtmlEditor.white = [
1261 'area', 'br', 'img', 'input', 'hr', 'wbr',
1263 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1264 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1265 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1266 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1267 'table', 'ul', 'xmp',
1269 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1272 'dir', 'menu', 'ol', 'ul', 'dl',
1278 Roo.form.HtmlEditor.black = [
1279 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1281 'base', 'basefont', 'bgsound', 'blink', 'body',
1282 'frame', 'frameset', 'head', 'html', 'ilayer',
1283 'iframe', 'layer', 'link', 'meta', 'object',
1284 'script', 'style' ,'title', 'xml' // clean later..
1286 Roo.form.HtmlEditor.clean = [
1287 'script', 'style', 'title', 'xml'
1289 Roo.form.HtmlEditor.remove = [
1294 Roo.form.HtmlEditor.ablack = [
1298 Roo.form.HtmlEditor.aclean = [
1299 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1303 Roo.form.HtmlEditor.pwhite= [
1304 'http', 'https', 'mailto'
1307 // white listed style attributes.
1308 Roo.form.HtmlEditor.cwhite= [
1314 Roo.form.HtmlEditor.swapCodes =[