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.
70 validationEvent : false,
74 sourceEditMode : false,
75 onFocus : Roo.emptyFn,
79 defaultAutoCreate : { // modified by initCompnoent..
81 style:"width:500px;height:300px;",
86 initComponent : function(){
90 * Fires when the editor is fully initialized (including the iframe)
91 * @param {HtmlEditor} this
96 * Fires when the editor is first receives the focus. Any insertion must wait
97 * until after this event.
98 * @param {HtmlEditor} this
103 * Fires before the textarea is updated with content from the editor iframe. Return false
104 * to cancel the sync.
105 * @param {HtmlEditor} this
106 * @param {String} html
111 * Fires before the iframe editor is updated with content from the textarea. Return false
112 * to cancel the push.
113 * @param {HtmlEditor} this
114 * @param {String} html
119 * Fires when the textarea is updated with content from the editor iframe.
120 * @param {HtmlEditor} this
121 * @param {String} html
126 * Fires when the iframe editor is updated with content from the textarea.
127 * @param {HtmlEditor} this
128 * @param {String} html
132 * @event editmodechange
133 * Fires when the editor switches edit modes
134 * @param {HtmlEditor} this
135 * @param {Boolean} sourceEdit True if source edit, false if standard editing.
137 editmodechange: true,
140 * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
141 * @param {HtmlEditor} this
145 this.defaultAutoCreate = {
147 style:'width: ' + this.width + 'px;height: ' + this.height + 'px;',
153 * Protected method that will not generally be called directly. It
154 * is called when the editor creates its toolbar. Override this method if you need to
155 * add custom toolbar buttons.
156 * @param {HtmlEditor} editor
158 createToolbar : function(editor){
159 if (!editor.toolbars || !editor.toolbars.length) {
160 editor.toolbars = [ new Roo.form.HtmlEditor.ToolbarStandard() ]; // can be empty?
163 for (var i =0 ; i < editor.toolbars.length;i++) {
164 editor.toolbars[i] = Roo.factory(editor.toolbars[i], Roo.form.HtmlEditor);
165 editor.toolbars[i].init(editor);
172 * Protected method that will not generally be called directly. It
173 * is called when the editor initializes the iframe with HTML contents. Override this method if you
174 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
176 getDocMarkup : function(){
179 Roo.get(document.head).select('style').each(function(node) {
180 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
183 Roo.get(document.head).select('link').each(function(node) {
184 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
187 return '<html><head>' + st +
188 //<style type="text/css">' +
189 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
191 ' </head><body></body></html>';
195 onRender : function(ct, position)
198 Roo.form.HtmlEditor.superclass.onRender.call(this, ct, position);
199 this.el.dom.style.border = '0 none';
200 this.el.dom.setAttribute('tabIndex', -1);
201 this.el.addClass('x-hidden');
202 if(Roo.isIE){ // fix IE 1px bogus margin
203 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
205 this.wrap = this.el.wrap({
206 cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
209 if (this.resizable) {
210 this.resizeEl = new Roo.Resizable(this.wrap, {
214 minHeight : this.height,
216 handles : this.resizable,
219 resize : function(r, w, h) {
220 _t.onResize(w,h); // -something
227 this.frameId = Roo.id();
229 this.createToolbar(this);
233 var iframe = this.wrap.createChild({
238 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
242 // console.log(iframe);
243 //this.wrap.dom.appendChild(iframe);
245 this.iframe = iframe.dom;
249 this.doc.designMode = 'on';
252 this.doc.write(this.getDocMarkup());
256 var task = { // must defer to wait for browser to be ready
258 //console.log("run task?" + this.doc.readyState);
260 if(this.doc.body || this.doc.readyState == 'complete'){
262 this.doc.designMode="on";
266 Roo.TaskMgr.stop(task);
267 this.initEditor.defer(10, this);
274 Roo.TaskMgr.start(task);
277 this.setSize(this.wrap.getSize());
280 this.resizeEl.resizeTo.defer(100, this.resizeEl,[ this.width,this.height ] );
281 // should trigger onReize..
286 onResize : function(w, h)
288 //Roo.log('resize: ' +w + ',' + h );
289 Roo.form.HtmlEditor.superclass.onResize.apply(this, arguments);
290 if(this.el && this.iframe){
291 if(typeof w == 'number'){
292 var aw = w - this.wrap.getFrameWidth('lr');
293 this.el.setWidth(this.adjustWidth('textarea', aw));
294 this.iframe.style.width = aw + 'px';
296 if(typeof h == 'number'){
298 for (var i =0; i < this.toolbars.length;i++) {
299 // fixme - ask toolbars for heights?
300 tbh += this.toolbars[i].tb.el.getHeight();
301 if (this.toolbars[i].footer) {
302 tbh += this.toolbars[i].footer.el.getHeight();
309 var ah = h - this.wrap.getFrameWidth('tb') - tbh;// this.tb.el.getHeight();
310 ah -= 5; // knock a few pixes off for look..
311 this.el.setHeight(this.adjustWidth('textarea', ah));
312 this.iframe.style.height = ah + 'px';
314 (this.doc.body || this.doc.documentElement).style.height = (ah - (this.iframePad*2)) + 'px';
321 * Toggles the editor between standard and source edit mode.
322 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
324 toggleSourceEdit : function(sourceEditMode){
326 this.sourceEditMode = sourceEditMode === true;
328 if(this.sourceEditMode){
331 this.iframe.className = 'x-hidden';
332 this.el.removeClass('x-hidden');
333 this.el.dom.removeAttribute('tabIndex');
338 this.iframe.className = '';
339 this.el.addClass('x-hidden');
340 this.el.dom.setAttribute('tabIndex', -1);
343 this.setSize(this.wrap.getSize());
344 this.fireEvent('editmodechange', this, this.sourceEditMode);
347 // private used internally
348 createLink : function(){
349 var url = prompt(this.createLinkText, this.defaultLinkValue);
350 if(url && url != 'http:/'+'/'){
351 this.relayCmd('createlink', url);
355 // private (for BoxComponent)
356 adjustSize : Roo.BoxComponent.prototype.adjustSize,
358 // private (for BoxComponent)
359 getResizeEl : function(){
363 // private (for BoxComponent)
364 getPositionEl : function(){
369 initEvents : function(){
370 this.originalValue = this.getValue();
374 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
377 markInvalid : Roo.emptyFn,
379 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
382 clearInvalid : Roo.emptyFn,
384 setValue : function(v){
385 Roo.form.HtmlEditor.superclass.setValue.call(this, v);
390 * Protected method that will not generally be called directly. If you need/want
391 * custom HTML cleanup, this is the method you should override.
392 * @param {String} html The HTML to be cleaned
393 * return {String} The cleaned HTML
395 cleanHtml : function(html){
398 if(Roo.isSafari){ // strip safari nonsense
399 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
402 if(html == ' '){
409 * Protected method that will not generally be called directly. Syncs the contents
410 * of the editor iframe with the textarea.
412 syncValue : function(){
413 if(this.initialized){
414 var bd = (this.doc.body || this.doc.documentElement);
416 var html = bd.innerHTML;
418 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
419 var m = bs.match(/text-align:(.*?);/i);
421 html = '<div style="'+m[0]+'">' + html + '</div>';
424 html = this.cleanHtml(html);
425 if(this.fireEvent('beforesync', this, html) !== false){
426 this.el.dom.value = html;
427 this.fireEvent('sync', this, html);
433 * Protected method that will not generally be called directly. Pushes the value of the textarea
434 * into the iframe editor.
436 pushValue : function(){
437 if(this.initialized){
438 var v = this.el.dom.value;
443 if(this.fireEvent('beforepush', this, v) !== false){
444 var d = (this.doc.body || this.doc.documentElement);
447 this.el.dom.value = d.innerHTML;
448 this.fireEvent('push', this, v);
454 deferFocus : function(){
455 this.focus.defer(10, this);
460 if(this.win && !this.sourceEditMode){
467 assignDocWin: function()
469 var iframe = this.iframe;
472 this.doc = iframe.contentWindow.document;
473 this.win = iframe.contentWindow;
475 if (!Roo.get(this.frameId)) {
478 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
479 this.win = Roo.get(this.frameId).dom.contentWindow;
484 initEditor : function(){
485 //console.log("INIT EDITOR");
490 this.doc.designMode="on";
492 this.doc.write(this.getDocMarkup());
495 var dbody = (this.doc.body || this.doc.documentElement);
496 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
497 // this copies styles from the containing element into thsi one..
498 // not sure why we need all of this..
499 var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
500 ss['background-attachment'] = 'fixed'; // w3c
501 dbody.bgProperties = 'fixed'; // ie
502 Roo.DomHelper.applyStyles(dbody, ss);
503 Roo.EventManager.on(this.doc, {
504 //'mousedown': this.onEditorEvent,
505 'mouseup': this.onEditorEvent,
506 'dblclick': this.onEditorEvent,
507 'click': this.onEditorEvent,
508 'keyup': this.onEditorEvent,
513 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
515 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
516 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
518 this.initialized = true;
520 this.fireEvent('initialize', this);
525 onDestroy : function(){
531 for (var i =0; i < this.toolbars.length;i++) {
532 // fixme - ask toolbars for heights?
533 this.toolbars[i].onDestroy();
536 this.wrap.dom.innerHTML = '';
542 onFirstFocus : function(){
547 this.activated = true;
548 for (var i =0; i < this.toolbars.length;i++) {
549 this.toolbars[i].onFirstFocus();
552 if(Roo.isGecko){ // prevent silly gecko errors
554 var s = this.win.getSelection();
555 if(!s.focusNode || s.focusNode.nodeType != 3){
556 var r = s.getRangeAt(0);
557 r.selectNodeContents((this.doc.body || this.doc.documentElement));
562 this.execCmd('useCSS', true);
563 this.execCmd('styleWithCSS', false);
566 this.fireEvent('activate', this);
570 adjustFont: function(btn){
571 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
572 //if(Roo.isSafari){ // safari
575 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
576 if(Roo.isSafari){ // safari
577 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
578 v = (v < 10) ? 10 : v;
579 v = (v > 48) ? 48 : v;
580 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
585 v = Math.max(1, v+adjust);
587 this.execCmd('FontSize', v );
590 onEditorEvent : function(e){
591 this.fireEvent('editorevent', this, e);
592 // this.updateToolbar();
596 insertTag : function(tg)
598 // could be a bit smarter... -> wrap the current selected tRoo..
600 this.execCmd("formatblock", tg);
604 insertText : function(txt)
608 range = this.createRange();
609 range.deleteContents();
610 //alert(Sender.getAttribute('label'));
612 range.insertNode(this.doc.createTextNode(txt));
616 relayBtnCmd : function(btn){
617 this.relayCmd(btn.cmd);
621 * Executes a Midas editor command on the editor document and performs necessary focus and
622 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
623 * @param {String} cmd The Midas command
624 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
626 relayCmd : function(cmd, value){
628 this.execCmd(cmd, value);
629 this.fireEvent('editorevent', this);
630 //this.updateToolbar();
635 * Executes a Midas editor command directly on the editor document.
636 * For visual commands, you should use {@link #relayCmd} instead.
637 * <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 execCmd : function(cmd, value){
642 this.doc.execCommand(cmd, false, value === undefined ? null : value);
648 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
650 * @param {String} text
652 insertAtCursor : function(text){
658 var r = this.doc.selection.createRange();
665 }else if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
667 this.execCmd('InsertHTML', text);
672 mozKeyPress : function(e){
674 var c = e.getCharCode(), cmd;
677 c = String.fromCharCode(c).toLowerCase();
688 this.cleanUpPaste.defer(100, this);
704 fixKeys : function(){ // load time branching for fastest keydown performance
707 var k = e.getKey(), r;
710 r = this.doc.selection.createRange();
713 r.pasteHTML('    ');
720 r = this.doc.selection.createRange();
722 var target = r.parentElement();
723 if(!target || target.tagName.toLowerCase() != 'li'){
725 r.pasteHTML('<br />');
731 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
732 this.cleanUpPaste.defer(100, this);
738 }else if(Roo.isOpera){
744 this.execCmd('InsertHTML','    ');
747 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
748 this.cleanUpPaste.defer(100, this);
753 }else if(Roo.isSafari){
759 this.execCmd('InsertText','\t');
763 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
764 this.cleanUpPaste.defer(100, this);
772 getAllAncestors: function()
774 var p = this.getSelectedNode();
777 a.push(p); // push blank onto stack..
778 p = this.getParentElement();
782 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
786 a.push(this.doc.body);
793 getSelection : function()
796 return Roo.isIE ? this.doc.selection : this.win.getSelection();
799 getSelectedNode: function()
801 // this may only work on Gecko!!!
803 // should we cache this!!!!
808 var range = this.createRange(this.getSelection());
811 var parent = range.parentElement();
813 var testRange = range.duplicate();
814 testRange.moveToElementText(parent);
815 if (testRange.inRange(range)) {
818 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
821 parent = parent.parentElement;
826 // is ancestor a text element.
827 var ac = range.commonAncestorContainer;
828 if (ac.nodeType == 3) {
832 var ar = ac.childNodes;
835 var other_nodes = [];
836 var has_other_nodes = false;
837 for (var i=0;i<ar.length;i++) {
838 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
841 // fullly contained node.
843 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
848 // probably selected..
849 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
850 other_nodes.push(ar[i]);
854 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
859 has_other_nodes = true;
861 if (!nodes.length && other_nodes.length) {
864 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
870 createRange: function(sel)
872 // this has strange effects when using with
873 // top toolbar - not sure if it's a great idea.
874 //this.editor.contentWindow.focus();
875 if (typeof sel != "undefined") {
877 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
879 return this.doc.createRange();
882 return this.doc.createRange();
885 getParentElement: function()
889 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
891 var range = this.createRange(sel);
894 var p = range.commonAncestorContainer;
895 while (p.nodeType == 3) { // text node
906 * Range intersection.. the hard stuff...
910 * [ -- selected range --- ]
914 * if end is before start or hits it. fail.
915 * if start is after end or hits it fail.
917 * if either hits (but other is outside. - then it's not
923 // @see http://www.thismuchiknow.co.uk/?p=64.
924 rangeIntersectsNode : function(range, node)
926 var nodeRange = node.ownerDocument.createRange();
928 nodeRange.selectNode(node);
930 nodeRange.selectNodeContents(node);
933 var rangeStartRange = range.cloneRange();
934 rangeStartRange.collapse(true);
936 var rangeEndRange = range.cloneRange();
937 rangeEndRange.collapse(false);
939 var nodeStartRange = nodeRange.cloneRange();
940 nodeStartRange.collapse(true);
942 var nodeEndRange = nodeRange.cloneRange();
943 nodeEndRange.collapse(false);
945 return rangeStartRange.compareBoundaryPoints(
946 Range.START_TO_START, nodeEndRange) == -1 &&
947 rangeEndRange.compareBoundaryPoints(
948 Range.START_TO_START, nodeStartRange) == 1;
952 rangeCompareNode : function(range, node)
954 var nodeRange = node.ownerDocument.createRange();
956 nodeRange.selectNode(node);
958 nodeRange.selectNodeContents(node);
962 range.collapse(true);
964 nodeRange.collapse(true);
966 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
967 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
969 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
971 var nodeIsBefore = ss == 1;
972 var nodeIsAfter = ee == -1;
974 if (nodeIsBefore && nodeIsAfter)
976 if (!nodeIsBefore && nodeIsAfter)
977 return 1; //right trailed.
979 if (nodeIsBefore && !nodeIsAfter)
980 return 2; // left trailed.
985 // private? - in a new class?
986 cleanUpPaste : function()
988 // cleans up the whole document..
989 // console.log('cleanuppaste');
990 this.cleanUpChildren(this.doc.body);
994 cleanUpChildren : function (n)
996 if (!n.childNodes.length) {
999 for (var i = n.childNodes.length-1; i > -1 ; i--) {
1000 this.cleanUpChild(n.childNodes[i]);
1007 cleanUpChild : function (node)
1009 //console.log(node);
1010 if (node.nodeName == "#text") {
1011 // clean up silly Windows -- stuff?
1014 if (node.nodeName == "#comment") {
1015 node.parentNode.removeChild(node);
1016 // clean up silly Windows -- stuff?
1020 if (Roo.form.HtmlEditor.black.indexOf(node.tagName.toLowerCase()) > -1) {
1022 node.parentNode.removeChild(node);
1026 if (Roo.form.HtmlEditor.remove.indexOf(node.tagName.toLowerCase()) > -1) {
1027 this.cleanUpChildren(node);
1028 // inserts everything just before this node...
1029 while (node.childNodes.length) {
1030 var cn = node.childNodes[0];
1031 node.removeChild(cn);
1032 node.parentNode.insertBefore(cn, node);
1034 node.parentNode.removeChild(node);
1038 if (!node.attributes || !node.attributes.length) {
1039 this.cleanUpChildren(node);
1043 function cleanAttr(n,v)
1046 if (v.match(/^\./) || v.match(/^\//)) {
1049 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1052 Roo.log("(REMOVE)"+ node.tagName +'.' + n + '=' + v);
1053 node.removeAttribute(n);
1057 function cleanStyle(n,v)
1059 if (v.match(/expression/)) { //XSS?? should we even bother..
1060 node.removeAttribute(n);
1065 var parts = v.split(/;/);
1066 Roo.each(parts, function(p) {
1067 p = p.replace(/\s+/g,'');
1071 var l = p.split(':').shift().replace(/\s+/g,'');
1073 // only allow 'c whitelisted system attributes'
1074 if (Roo.form.HtmlEditor.cwhite.indexOf(l) < 0) {
1075 Roo.log('(REMOVE)' + node.tagName +'.' + n + ':'+l + '=' + v);
1076 node.removeAttribute(n);
1086 for (var i = node.attributes.length-1; i > -1 ; i--) {
1087 var a = node.attributes[i];
1089 if (Roo.form.HtmlEditor.ablack.indexOf(a.name.toLowerCase()) > -1) {
1090 node.removeAttribute(a.name);
1093 if (Roo.form.HtmlEditor.aclean.indexOf(a.name.toLowerCase()) > -1) {
1094 cleanAttr(a.name,a.value); // fixme..
1097 if (a.name == 'style') {
1098 cleanStyle(a.name,a.value);
1100 /// clean up MS crap..
1101 if (a.name == 'class') {
1102 if (a.value.match(/^Mso/)) {
1103 node.className = '';
1113 this.cleanUpChildren(node);
1119 // hide stuff that is not compatible
1137 * @cfg {String} fieldClass @hide
1140 * @cfg {String} focusClass @hide
1143 * @cfg {String} autoCreate @hide
1146 * @cfg {String} inputType @hide
1149 * @cfg {String} invalidClass @hide
1152 * @cfg {String} invalidText @hide
1155 * @cfg {String} msgFx @hide
1158 * @cfg {String} validateOnBlur @hide
1162 Roo.form.HtmlEditor.white = [
1163 'area', 'br', 'img', 'input', 'hr', 'wbr',
1165 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1166 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1167 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1168 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1169 'table', 'ul', 'xmp',
1171 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1174 'dir', 'menu', 'ol', 'ul', 'dl',
1180 Roo.form.HtmlEditor.black = [
1181 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1183 'base', 'basefont', 'bgsound', 'blink', 'body',
1184 'frame', 'frameset', 'head', 'html', 'ilayer',
1185 'iframe', 'layer', 'link', 'meta', 'object',
1186 'script', 'style' ,'title', 'xml' // clean later..
1188 Roo.form.HtmlEditor.clean = [
1189 'script', 'style', 'title', 'xml'
1191 Roo.form.HtmlEditor.remove = [
1196 Roo.form.HtmlEditor.ablack = [
1200 Roo.form.HtmlEditor.aclean = [
1201 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1205 Roo.form.HtmlEditor.pwhite= [
1206 'http', 'https', 'mailto'
1209 // white listed style attributes.
1210 Roo.form.HtmlEditor.cwhite= [