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)
64 validationEvent : false,
68 sourceEditMode : false,
69 onFocus : Roo.emptyFn,
73 defaultAutoCreate : { // modified by initCompnoent..
75 style:"width:500px;height:300px;",
80 initComponent : function(){
84 * Fires when the editor is fully initialized (including the iframe)
85 * @param {HtmlEditor} this
90 * Fires when the editor is first receives the focus. Any insertion must wait
91 * until after this event.
92 * @param {HtmlEditor} this
97 * Fires before the textarea is updated with content from the editor iframe. Return false
99 * @param {HtmlEditor} this
100 * @param {String} html
105 * Fires before the iframe editor is updated with content from the textarea. Return false
106 * to cancel the push.
107 * @param {HtmlEditor} this
108 * @param {String} html
113 * Fires when the textarea is updated with content from the editor iframe.
114 * @param {HtmlEditor} this
115 * @param {String} html
120 * Fires when the iframe editor is updated with content from the textarea.
121 * @param {HtmlEditor} this
122 * @param {String} html
126 * @event editmodechange
127 * Fires when the editor switches edit modes
128 * @param {HtmlEditor} this
129 * @param {Boolean} sourceEdit True if source edit, false if standard editing.
131 editmodechange: true,
134 * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
135 * @param {HtmlEditor} this
139 this.defaultAutoCreate = {
141 style:'width: ' + this.width + 'px;height: ' + this.height + 'px;',
147 * Protected method that will not generally be called directly. It
148 * is called when the editor creates its toolbar. Override this method if you need to
149 * add custom toolbar buttons.
150 * @param {HtmlEditor} editor
152 createToolbar : function(editor){
153 if (!editor.toolbars || !editor.toolbars.length) {
154 editor.toolbars = [ new Roo.form.HtmlEditor.ToolbarStandard() ]; // can be empty?
157 for (var i =0 ; i < editor.toolbars.length;i++) {
158 editor.toolbars[i] = Roo.factory(editor.toolbars[i], Roo.form.HtmlEditor);
159 editor.toolbars[i].init(editor);
166 * Protected method that will not generally be called directly. It
167 * is called when the editor initializes the iframe with HTML contents. Override this method if you
168 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
170 getDocMarkup : function(){
173 Roo.get(document.head).select('style').each(function(e) {
174 st += "\n" + e.innerHTML;
178 return '<html><head><style type="text/css">' +
179 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
180 '</style></head><body></body></html>';
184 onRender : function(ct, position)
187 Roo.form.HtmlEditor.superclass.onRender.call(this, ct, position);
188 this.el.dom.style.border = '0 none';
189 this.el.dom.setAttribute('tabIndex', -1);
190 this.el.addClass('x-hidden');
191 if(Roo.isIE){ // fix IE 1px bogus margin
192 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
194 this.wrap = this.el.wrap({
195 cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
198 if (this.resizable) {
199 this.resizeEl = new Roo.Resizable(this.wrap, {
203 minHeight : this.height,
205 handles : this.resizable,
208 resize : function(r, w, h) {
209 _t.onResize(w,h); // -something
216 this.frameId = Roo.id();
218 this.createToolbar(this);
222 var iframe = this.wrap.createChild({
227 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
231 // console.log(iframe);
232 //this.wrap.dom.appendChild(iframe);
234 this.iframe = iframe.dom;
238 this.doc.designMode = 'on';
241 this.doc.write(this.getDocMarkup());
245 var task = { // must defer to wait for browser to be ready
247 //console.log("run task?" + this.doc.readyState);
249 if(this.doc.body || this.doc.readyState == 'complete'){
251 this.doc.designMode="on";
255 Roo.TaskMgr.stop(task);
256 this.initEditor.defer(10, this);
263 Roo.TaskMgr.start(task);
266 this.setSize(this.wrap.getSize());
269 this.resizeEl.resizeTo.defer(100, this.resizeEl,[ this.width,this.height ] );
270 // should trigger onReize..
275 onResize : function(w, h)
277 //Roo.log('resize: ' +w + ',' + h );
278 Roo.form.HtmlEditor.superclass.onResize.apply(this, arguments);
279 if(this.el && this.iframe){
280 if(typeof w == 'number'){
281 var aw = w - this.wrap.getFrameWidth('lr');
282 this.el.setWidth(this.adjustWidth('textarea', aw));
283 this.iframe.style.width = aw + 'px';
285 if(typeof h == 'number'){
287 for (var i =0; i < this.toolbars.length;i++) {
288 // fixme - ask toolbars for heights?
289 tbh += this.toolbars[i].tb.el.getHeight();
290 if (this.toolbars[i].footer) {
291 tbh += this.toolbars[i].footer.el.getHeight();
298 var ah = h - this.wrap.getFrameWidth('tb') - tbh;// this.tb.el.getHeight();
299 ah -= 5; // knock a few pixes off for look..
300 this.el.setHeight(this.adjustWidth('textarea', ah));
301 this.iframe.style.height = ah + 'px';
303 (this.doc.body || this.doc.documentElement).style.height = (ah - (this.iframePad*2)) + 'px';
310 * Toggles the editor between standard and source edit mode.
311 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
313 toggleSourceEdit : function(sourceEditMode){
315 this.sourceEditMode = sourceEditMode === true;
317 if(this.sourceEditMode){
320 this.iframe.className = 'x-hidden';
321 this.el.removeClass('x-hidden');
322 this.el.dom.removeAttribute('tabIndex');
327 this.iframe.className = '';
328 this.el.addClass('x-hidden');
329 this.el.dom.setAttribute('tabIndex', -1);
332 this.setSize(this.wrap.getSize());
333 this.fireEvent('editmodechange', this, this.sourceEditMode);
336 // private used internally
337 createLink : function(){
338 var url = prompt(this.createLinkText, this.defaultLinkValue);
339 if(url && url != 'http:/'+'/'){
340 this.relayCmd('createlink', url);
344 // private (for BoxComponent)
345 adjustSize : Roo.BoxComponent.prototype.adjustSize,
347 // private (for BoxComponent)
348 getResizeEl : function(){
352 // private (for BoxComponent)
353 getPositionEl : function(){
358 initEvents : function(){
359 this.originalValue = this.getValue();
363 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
366 markInvalid : Roo.emptyFn,
368 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
371 clearInvalid : Roo.emptyFn,
373 setValue : function(v){
374 Roo.form.HtmlEditor.superclass.setValue.call(this, v);
379 * Protected method that will not generally be called directly. If you need/want
380 * custom HTML cleanup, this is the method you should override.
381 * @param {String} html The HTML to be cleaned
382 * return {String} The cleaned HTML
384 cleanHtml : function(html){
387 if(Roo.isSafari){ // strip safari nonsense
388 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
391 if(html == ' '){
398 * Protected method that will not generally be called directly. Syncs the contents
399 * of the editor iframe with the textarea.
401 syncValue : function(){
402 if(this.initialized){
403 var bd = (this.doc.body || this.doc.documentElement);
405 var html = bd.innerHTML;
407 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
408 var m = bs.match(/text-align:(.*?);/i);
410 html = '<div style="'+m[0]+'">' + html + '</div>';
413 html = this.cleanHtml(html);
414 if(this.fireEvent('beforesync', this, html) !== false){
415 this.el.dom.value = html;
416 this.fireEvent('sync', this, html);
422 * Protected method that will not generally be called directly. Pushes the value of the textarea
423 * into the iframe editor.
425 pushValue : function(){
426 if(this.initialized){
427 var v = this.el.dom.value;
432 if(this.fireEvent('beforepush', this, v) !== false){
433 var d = (this.doc.body || this.doc.documentElement);
436 this.el.dom.value = d.innerHTML;
437 this.fireEvent('push', this, v);
443 deferFocus : function(){
444 this.focus.defer(10, this);
449 if(this.win && !this.sourceEditMode){
456 assignDocWin: function()
458 var iframe = this.iframe;
461 this.doc = iframe.contentWindow.document;
462 this.win = iframe.contentWindow;
464 if (!Roo.get(this.frameId)) {
467 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
468 this.win = Roo.get(this.frameId).dom.contentWindow;
473 initEditor : function(){
474 //console.log("INIT EDITOR");
479 this.doc.designMode="on";
481 this.doc.write(this.getDocMarkup());
484 var dbody = (this.doc.body || this.doc.documentElement);
485 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
486 // this copies styles from the containing element into thsi one..
487 // not sure why we need all of this..
488 var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
489 ss['background-attachment'] = 'fixed'; // w3c
490 dbody.bgProperties = 'fixed'; // ie
491 Roo.DomHelper.applyStyles(dbody, ss);
492 Roo.EventManager.on(this.doc, {
493 //'mousedown': this.onEditorEvent,
494 'mouseup': this.onEditorEvent,
495 'dblclick': this.onEditorEvent,
496 'click': this.onEditorEvent,
497 'keyup': this.onEditorEvent,
502 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
504 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
505 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
507 this.initialized = true;
509 this.fireEvent('initialize', this);
514 onDestroy : function(){
520 for (var i =0; i < this.toolbars.length;i++) {
521 // fixme - ask toolbars for heights?
522 this.toolbars[i].onDestroy();
525 this.wrap.dom.innerHTML = '';
531 onFirstFocus : function(){
536 this.activated = true;
537 for (var i =0; i < this.toolbars.length;i++) {
538 this.toolbars[i].onFirstFocus();
541 if(Roo.isGecko){ // prevent silly gecko errors
543 var s = this.win.getSelection();
544 if(!s.focusNode || s.focusNode.nodeType != 3){
545 var r = s.getRangeAt(0);
546 r.selectNodeContents((this.doc.body || this.doc.documentElement));
551 this.execCmd('useCSS', true);
552 this.execCmd('styleWithCSS', false);
555 this.fireEvent('activate', this);
559 adjustFont: function(btn){
560 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
561 //if(Roo.isSafari){ // safari
564 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
565 if(Roo.isSafari){ // safari
566 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
567 v = (v < 10) ? 10 : v;
568 v = (v > 48) ? 48 : v;
569 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
574 v = Math.max(1, v+adjust);
576 this.execCmd('FontSize', v );
579 onEditorEvent : function(e){
580 this.fireEvent('editorevent', this, e);
581 // this.updateToolbar();
585 insertTag : function(tg)
587 // could be a bit smarter... -> wrap the current selected tRoo..
589 this.execCmd("formatblock", tg);
593 insertText : function(txt)
597 range = this.createRange();
598 range.deleteContents();
599 //alert(Sender.getAttribute('label'));
601 range.insertNode(this.doc.createTextNode(txt));
605 relayBtnCmd : function(btn){
606 this.relayCmd(btn.cmd);
610 * Executes a Midas editor command on the editor document and performs necessary focus and
611 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
612 * @param {String} cmd The Midas command
613 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
615 relayCmd : function(cmd, value){
617 this.execCmd(cmd, value);
618 this.fireEvent('editorevent', this);
619 //this.updateToolbar();
624 * Executes a Midas editor command directly on the editor document.
625 * For visual commands, you should use {@link #relayCmd} instead.
626 * <b>This should only be called after the editor is initialized.</b>
627 * @param {String} cmd The Midas command
628 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
630 execCmd : function(cmd, value){
631 this.doc.execCommand(cmd, false, value === undefined ? null : value);
637 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
639 * @param {String} text
641 insertAtCursor : function(text){
647 var r = this.doc.selection.createRange();
654 }else if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
656 this.execCmd('InsertHTML', text);
661 mozKeyPress : function(e){
663 var c = e.getCharCode(), cmd;
666 c = String.fromCharCode(c).toLowerCase();
677 this.cleanUpPaste.defer(100, this);
693 fixKeys : function(){ // load time branching for fastest keydown performance
696 var k = e.getKey(), r;
699 r = this.doc.selection.createRange();
702 r.pasteHTML('    ');
709 r = this.doc.selection.createRange();
711 var target = r.parentElement();
712 if(!target || target.tagName.toLowerCase() != 'li'){
714 r.pasteHTML('<br />');
720 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
721 this.cleanUpPaste.defer(100, this);
727 }else if(Roo.isOpera){
733 this.execCmd('InsertHTML','    ');
736 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
737 this.cleanUpPaste.defer(100, this);
742 }else if(Roo.isSafari){
748 this.execCmd('InsertText','\t');
752 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
753 this.cleanUpPaste.defer(100, this);
761 getAllAncestors: function()
763 var p = this.getSelectedNode();
766 a.push(p); // push blank onto stack..
767 p = this.getParentElement();
771 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
775 a.push(this.doc.body);
782 getSelection : function()
785 return Roo.isIE ? this.doc.selection : this.win.getSelection();
788 getSelectedNode: function()
790 // this may only work on Gecko!!!
792 // should we cache this!!!!
797 var range = this.createRange(this.getSelection());
800 var parent = range.parentElement();
802 var testRange = range.duplicate();
803 testRange.moveToElementText(parent);
804 if (testRange.inRange(range)) {
807 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
810 parent = parent.parentElement;
815 // is ancestor a text element.
816 var ac = range.commonAncestorContainer;
817 if (ac.nodeType == 3) {
821 var ar = ac.childNodes;
824 var other_nodes = [];
825 var has_other_nodes = false;
826 for (var i=0;i<ar.length;i++) {
827 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
830 // fullly contained node.
832 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
837 // probably selected..
838 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
839 other_nodes.push(ar[i]);
843 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
848 has_other_nodes = true;
850 if (!nodes.length && other_nodes.length) {
853 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
859 createRange: function(sel)
861 // this has strange effects when using with
862 // top toolbar - not sure if it's a great idea.
863 //this.editor.contentWindow.focus();
864 if (typeof sel != "undefined") {
866 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
868 return this.doc.createRange();
871 return this.doc.createRange();
874 getParentElement: function()
878 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
880 var range = this.createRange(sel);
883 var p = range.commonAncestorContainer;
884 while (p.nodeType == 3) { // text node
895 * Range intersection.. the hard stuff...
899 * [ -- selected range --- ]
903 * if end is before start or hits it. fail.
904 * if start is after end or hits it fail.
906 * if either hits (but other is outside. - then it's not
912 // @see http://www.thismuchiknow.co.uk/?p=64.
913 rangeIntersectsNode : function(range, node)
915 var nodeRange = node.ownerDocument.createRange();
917 nodeRange.selectNode(node);
919 nodeRange.selectNodeContents(node);
922 var rangeStartRange = range.cloneRange();
923 rangeStartRange.collapse(true);
925 var rangeEndRange = range.cloneRange();
926 rangeEndRange.collapse(false);
928 var nodeStartRange = nodeRange.cloneRange();
929 nodeStartRange.collapse(true);
931 var nodeEndRange = nodeRange.cloneRange();
932 nodeEndRange.collapse(false);
934 return rangeStartRange.compareBoundaryPoints(
935 Range.START_TO_START, nodeEndRange) == -1 &&
936 rangeEndRange.compareBoundaryPoints(
937 Range.START_TO_START, nodeStartRange) == 1;
941 rangeCompareNode : function(range, node)
943 var nodeRange = node.ownerDocument.createRange();
945 nodeRange.selectNode(node);
947 nodeRange.selectNodeContents(node);
951 range.collapse(true);
953 nodeRange.collapse(true);
955 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
956 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
958 Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
960 var nodeIsBefore = ss == 1;
961 var nodeIsAfter = ee == -1;
963 if (nodeIsBefore && nodeIsAfter)
965 if (!nodeIsBefore && nodeIsAfter)
966 return 1; //right trailed.
968 if (nodeIsBefore && !nodeIsAfter)
969 return 2; // left trailed.
974 // private? - in a new class?
975 cleanUpPaste : function()
977 // cleans up the whole document..
978 // console.log('cleanuppaste');
979 this.cleanUpChildren(this.doc.body);
983 cleanUpChildren : function (n)
985 if (!n.childNodes.length) {
988 for (var i = n.childNodes.length-1; i > -1 ; i--) {
989 this.cleanUpChild(n.childNodes[i]);
996 cleanUpChild : function (node)
999 if (node.nodeName == "#text") {
1000 // clean up silly Windows -- stuff?
1003 if (node.nodeName == "#comment") {
1004 node.parentNode.removeChild(node);
1005 // clean up silly Windows -- stuff?
1009 if (Roo.form.HtmlEditor.black.indexOf(node.tagName.toLowerCase()) > -1) {
1011 node.parentNode.removeChild(node);
1015 if (Roo.form.HtmlEditor.remove.indexOf(node.tagName.toLowerCase()) > -1) {
1016 this.cleanUpChildren(node);
1017 // inserts everything just before this node...
1018 while (node.childNodes.length) {
1019 var cn = node.childNodes[0];
1020 node.removeChild(cn);
1021 node.parentNode.insertBefore(cn, node);
1023 node.parentNode.removeChild(node);
1027 if (!node.attributes || !node.attributes.length) {
1028 this.cleanUpChildren(node);
1032 function cleanAttr(n,v)
1035 if (v.match(/^\./) || v.match(/^\//)) {
1038 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1041 Roo.log("(REMOVE)"+ node.tagName +'.' + n + '=' + v);
1042 node.removeAttribute(n);
1046 function cleanStyle(n,v)
1048 if (v.match(/expression/)) { //XSS?? should we even bother..
1049 node.removeAttribute(n);
1054 var parts = v.split(/;/);
1055 Roo.each(parts, function(p) {
1056 p = p.replace(/\s+/g,'');
1060 var l = p.split(':').shift().replace(/\s+/g,'');
1062 // only allow 'c whitelisted system attributes'
1063 if (Roo.form.HtmlEditor.cwhite.indexOf(l) < 0) {
1064 Roo.log('(REMOVE)' + node.tagName +'.' + n + ':'+l + '=' + v);
1065 node.removeAttribute(n);
1075 for (var i = node.attributes.length-1; i > -1 ; i--) {
1076 var a = node.attributes[i];
1078 if (Roo.form.HtmlEditor.ablack.indexOf(a.name.toLowerCase()) > -1) {
1079 node.removeAttribute(a.name);
1082 if (Roo.form.HtmlEditor.aclean.indexOf(a.name.toLowerCase()) > -1) {
1083 cleanAttr(a.name,a.value); // fixme..
1086 if (a.name == 'style') {
1087 cleanStyle(a.name,a.value);
1089 /// clean up MS crap..
1090 if (a.name == 'class') {
1091 if (a.value.match(/^Mso/)) {
1092 node.className = '';
1102 this.cleanUpChildren(node);
1108 // hide stuff that is not compatible
1126 * @cfg {String} fieldClass @hide
1129 * @cfg {String} focusClass @hide
1132 * @cfg {String} autoCreate @hide
1135 * @cfg {String} inputType @hide
1138 * @cfg {String} invalidClass @hide
1141 * @cfg {String} invalidText @hide
1144 * @cfg {String} msgFx @hide
1147 * @cfg {String} validateOnBlur @hide
1151 Roo.form.HtmlEditor.white = [
1152 'area', 'br', 'img', 'input', 'hr', 'wbr',
1154 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1155 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1156 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1157 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1158 'table', 'ul', 'xmp',
1160 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1163 'dir', 'menu', 'ol', 'ul', 'dl',
1169 Roo.form.HtmlEditor.black = [
1170 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1172 'base', 'basefont', 'bgsound', 'blink', 'body',
1173 'frame', 'frameset', 'head', 'html', 'ilayer',
1174 'iframe', 'layer', 'link', 'meta', 'object',
1175 'script', 'style' ,'title', 'xml' // clean later..
1177 Roo.form.HtmlEditor.clean = [
1178 'script', 'style', 'title', 'xml'
1180 Roo.form.HtmlEditor.remove = [
1185 Roo.form.HtmlEditor.ablack = [
1189 Roo.form.HtmlEditor.aclean = [
1190 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1194 Roo.form.HtmlEditor.pwhite= [
1195 'http', 'https', 'mailto'
1198 // white listed style attributes.
1199 Roo.form.HtmlEditor.cwhite= [