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.stylesheet.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);
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 if(this.fireEvent('beforesync', this, html) !== false){
441 this.el.dom.value = html;
442 this.fireEvent('sync', this, html);
448 * Protected method that will not generally be called directly. Pushes the value of the textarea
449 * into the iframe editor.
451 pushValue : function(){
452 if(this.initialized){
453 var v = this.el.dom.value;
458 if(this.fireEvent('beforepush', this, v) !== false){
459 var d = (this.doc.body || this.doc.documentElement);
462 this.el.dom.value = d.innerHTML;
463 this.fireEvent('push', this, v);
469 deferFocus : function(){
470 this.focus.defer(10, this);
475 if(this.win && !this.sourceEditMode){
482 assignDocWin: function()
484 var iframe = this.iframe;
487 this.doc = iframe.contentWindow.document;
488 this.win = iframe.contentWindow;
490 if (!Roo.get(this.frameId)) {
493 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
494 this.win = Roo.get(this.frameId).dom.contentWindow;
499 initEditor : function(){
500 //console.log("INIT EDITOR");
505 this.doc.designMode="on";
507 this.doc.write(this.getDocMarkup());
510 var dbody = (this.doc.body || this.doc.documentElement);
511 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
512 // this copies styles from the containing element into thsi one..
513 // not sure why we need all of this..
514 var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
515 ss['background-attachment'] = 'fixed'; // w3c
516 dbody.bgProperties = 'fixed'; // ie
517 Roo.DomHelper.applyStyles(dbody, ss);
518 Roo.EventManager.on(this.doc, {
519 //'mousedown': this.onEditorEvent,
520 'mouseup': this.onEditorEvent,
521 'dblclick': this.onEditorEvent,
522 'click': this.onEditorEvent,
523 'keyup': this.onEditorEvent,
528 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
530 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
531 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
533 this.initialized = true;
535 this.fireEvent('initialize', this);
540 onDestroy : function(){
546 for (var i =0; i < this.toolbars.length;i++) {
547 // fixme - ask toolbars for heights?
548 this.toolbars[i].onDestroy();
551 this.wrap.dom.innerHTML = '';
557 onFirstFocus : function(){
562 this.activated = true;
563 for (var i =0; i < this.toolbars.length;i++) {
564 this.toolbars[i].onFirstFocus();
567 if(Roo.isGecko){ // prevent silly gecko errors
569 var s = this.win.getSelection();
570 if(!s.focusNode || s.focusNode.nodeType != 3){
571 var r = s.getRangeAt(0);
572 r.selectNodeContents((this.doc.body || this.doc.documentElement));
577 this.execCmd('useCSS', true);
578 this.execCmd('styleWithCSS', false);
581 this.fireEvent('activate', this);
585 adjustFont: function(btn){
586 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
587 //if(Roo.isSafari){ // safari
590 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
591 if(Roo.isSafari){ // safari
592 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
593 v = (v < 10) ? 10 : v;
594 v = (v > 48) ? 48 : v;
595 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
600 v = Math.max(1, v+adjust);
602 this.execCmd('FontSize', v );
605 onEditorEvent : function(e){
606 this.fireEvent('editorevent', this, e);
607 // this.updateToolbar();
611 insertTag : function(tg)
613 // could be a bit smarter... -> wrap the current selected tRoo..
615 this.execCmd("formatblock", tg);
619 insertText : function(txt)
623 range = this.createRange();
624 range.deleteContents();
625 //alert(Sender.getAttribute('label'));
627 range.insertNode(this.doc.createTextNode(txt));
631 relayBtnCmd : function(btn){
632 this.relayCmd(btn.cmd);
636 * Executes a Midas editor command on the editor document and performs necessary focus and
637 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
638 * @param {String} cmd The Midas command
639 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
641 relayCmd : function(cmd, value){
643 this.execCmd(cmd, value);
644 this.fireEvent('editorevent', this);
645 //this.updateToolbar();
650 * Executes a Midas editor command directly on the editor document.
651 * For visual commands, you should use {@link #relayCmd} instead.
652 * <b>This should only be called after the editor is initialized.</b>
653 * @param {String} cmd The Midas command
654 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
656 execCmd : function(cmd, value){
657 this.doc.execCommand(cmd, false, value === undefined ? null : value);
663 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
665 * @param {String} text
667 insertAtCursor : function(text){
673 var r = this.doc.selection.createRange();
680 }else if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
682 this.execCmd('InsertHTML', text);
687 mozKeyPress : function(e){
689 var c = e.getCharCode(), cmd;
692 c = String.fromCharCode(c).toLowerCase();
703 this.cleanUpPaste.defer(100, this);
719 fixKeys : function(){ // load time branching for fastest keydown performance
722 var k = e.getKey(), r;
725 r = this.doc.selection.createRange();
728 r.pasteHTML('    ');
735 r = this.doc.selection.createRange();
737 var target = r.parentElement();
738 if(!target || target.tagName.toLowerCase() != 'li'){
740 r.pasteHTML('<br />');
746 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
747 this.cleanUpPaste.defer(100, this);
753 }else if(Roo.isOpera){
759 this.execCmd('InsertHTML','    ');
762 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
763 this.cleanUpPaste.defer(100, this);
768 }else if(Roo.isSafari){
774 this.execCmd('InsertText','\t');
778 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
779 this.cleanUpPaste.defer(100, this);
787 getAllAncestors: function()
789 var p = this.getSelectedNode();
792 a.push(p); // push blank onto stack..
793 p = this.getParentElement();
797 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
801 a.push(this.doc.body);
808 getSelection : function()
811 return Roo.isIE ? this.doc.selection : this.win.getSelection();
814 getSelectedNode: function()
816 // this may only work on Gecko!!!
818 // should we cache this!!!!
823 var range = this.createRange(this.getSelection());
826 var parent = range.parentElement();
828 var testRange = range.duplicate();
829 testRange.moveToElementText(parent);
830 if (testRange.inRange(range)) {
833 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
836 parent = parent.parentElement;
841 // is ancestor a text element.
842 var ac = range.commonAncestorContainer;
843 if (ac.nodeType == 3) {
847 var ar = ac.childNodes;
850 var other_nodes = [];
851 var has_other_nodes = false;
852 for (var i=0;i<ar.length;i++) {
853 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
856 // fullly contained node.
858 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
863 // probably selected..
864 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
865 other_nodes.push(ar[i]);
869 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
874 has_other_nodes = true;
876 if (!nodes.length && other_nodes.length) {
879 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
885 createRange: function(sel)
887 // this has strange effects when using with
888 // top toolbar - not sure if it's a great idea.
889 //this.editor.contentWindow.focus();
890 if (typeof sel != "undefined") {
892 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
894 return this.doc.createRange();
897 return this.doc.createRange();
900 getParentElement: function()
904 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
906 var range = this.createRange(sel);
909 var p = range.commonAncestorContainer;
910 while (p.nodeType == 3) { // text node
921 * Range intersection.. the hard stuff...
925 * [ -- selected range --- ]
929 * if end is before start or hits it. fail.
930 * if start is after end or hits it fail.
932 * if either hits (but other is outside. - then it's not
938 // @see http://www.thismuchiknow.co.uk/?p=64.
939 rangeIntersectsNode : function(range, node)
941 var nodeRange = node.ownerDocument.createRange();
943 nodeRange.selectNode(node);
945 nodeRange.selectNodeContents(node);
948 var rangeStartRange = range.cloneRange();
949 rangeStartRange.collapse(true);
951 var rangeEndRange = range.cloneRange();
952 rangeEndRange.collapse(false);
954 var nodeStartRange = nodeRange.cloneRange();
955 nodeStartRange.collapse(true);
957 var nodeEndRange = nodeRange.cloneRange();
958 nodeEndRange.collapse(false);
960 return rangeStartRange.compareBoundaryPoints(
961 Range.START_TO_START, nodeEndRange) == -1 &&
962 rangeEndRange.compareBoundaryPoints(
963 Range.START_TO_START, nodeStartRange) == 1;
967 rangeCompareNode : function(range, node)
969 var nodeRange = node.ownerDocument.createRange();
971 nodeRange.selectNode(node);
973 nodeRange.selectNodeContents(node);
977 range.collapse(true);
979 nodeRange.collapse(true);
981 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
982 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
984 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
986 var nodeIsBefore = ss == 1;
987 var nodeIsAfter = ee == -1;
989 if (nodeIsBefore && nodeIsAfter)
991 if (!nodeIsBefore && nodeIsAfter)
992 return 1; //right trailed.
994 if (nodeIsBefore && !nodeIsAfter)
995 return 2; // left trailed.
1000 // private? - in a new class?
1001 cleanUpPaste : function()
1003 // cleans up the whole document..
1004 // console.log('cleanuppaste');
1005 this.cleanUpChildren(this.doc.body);
1009 cleanUpChildren : function (n)
1011 if (!n.childNodes.length) {
1014 for (var i = n.childNodes.length-1; i > -1 ; i--) {
1015 this.cleanUpChild(n.childNodes[i]);
1022 cleanUpChild : function (node)
1024 //console.log(node);
1025 if (node.nodeName == "#text") {
1026 // clean up silly Windows -- stuff?
1029 if (node.nodeName == "#comment") {
1030 node.parentNode.removeChild(node);
1031 // clean up silly Windows -- stuff?
1035 if (Roo.form.HtmlEditor.black.indexOf(node.tagName.toLowerCase()) > -1) {
1037 node.parentNode.removeChild(node);
1041 if (Roo.form.HtmlEditor.remove.indexOf(node.tagName.toLowerCase()) > -1) {
1042 this.cleanUpChildren(node);
1043 // inserts everything just before this node...
1044 while (node.childNodes.length) {
1045 var cn = node.childNodes[0];
1046 node.removeChild(cn);
1047 node.parentNode.insertBefore(cn, node);
1049 node.parentNode.removeChild(node);
1053 if (!node.attributes || !node.attributes.length) {
1054 this.cleanUpChildren(node);
1058 function cleanAttr(n,v)
1061 if (v.match(/^\./) || v.match(/^\//)) {
1064 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1067 Roo.log("(REMOVE)"+ node.tagName +'.' + n + '=' + v);
1068 node.removeAttribute(n);
1072 function cleanStyle(n,v)
1074 if (v.match(/expression/)) { //XSS?? should we even bother..
1075 node.removeAttribute(n);
1080 var parts = v.split(/;/);
1081 Roo.each(parts, function(p) {
1082 p = p.replace(/\s+/g,'');
1086 var l = p.split(':').shift().replace(/\s+/g,'');
1088 // only allow 'c whitelisted system attributes'
1089 if (Roo.form.HtmlEditor.cwhite.indexOf(l) < 0) {
1090 Roo.log('(REMOVE)' + node.tagName +'.' + n + ':'+l + '=' + v);
1091 node.removeAttribute(n);
1101 for (var i = node.attributes.length-1; i > -1 ; i--) {
1102 var a = node.attributes[i];
1104 if (Roo.form.HtmlEditor.ablack.indexOf(a.name.toLowerCase()) > -1) {
1105 node.removeAttribute(a.name);
1108 if (Roo.form.HtmlEditor.aclean.indexOf(a.name.toLowerCase()) > -1) {
1109 cleanAttr(a.name,a.value); // fixme..
1112 if (a.name == 'style') {
1113 cleanStyle(a.name,a.value);
1115 /// clean up MS crap..
1116 if (a.name == 'class') {
1117 if (a.value.match(/^Mso/)) {
1118 node.className = '';
1128 this.cleanUpChildren(node);
1134 // hide stuff that is not compatible
1152 * @cfg {String} fieldClass @hide
1155 * @cfg {String} focusClass @hide
1158 * @cfg {String} autoCreate @hide
1161 * @cfg {String} inputType @hide
1164 * @cfg {String} invalidClass @hide
1167 * @cfg {String} invalidText @hide
1170 * @cfg {String} msgFx @hide
1173 * @cfg {String} validateOnBlur @hide
1177 Roo.form.HtmlEditor.white = [
1178 'area', 'br', 'img', 'input', 'hr', 'wbr',
1180 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1181 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1182 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1183 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1184 'table', 'ul', 'xmp',
1186 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1189 'dir', 'menu', 'ol', 'ul', 'dl',
1195 Roo.form.HtmlEditor.black = [
1196 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1198 'base', 'basefont', 'bgsound', 'blink', 'body',
1199 'frame', 'frameset', 'head', 'html', 'ilayer',
1200 'iframe', 'layer', 'link', 'meta', 'object',
1201 'script', 'style' ,'title', 'xml' // clean later..
1203 Roo.form.HtmlEditor.clean = [
1204 'script', 'style', 'title', 'xml'
1206 Roo.form.HtmlEditor.remove = [
1211 Roo.form.HtmlEditor.ablack = [
1215 Roo.form.HtmlEditor.aclean = [
1216 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1220 Roo.form.HtmlEditor.pwhite= [
1221 'http', 'https', 'mailto'
1224 // white listed style attributes.
1225 Roo.form.HtmlEditor.cwhite= [