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(){
172 var st = '<style type="text/css">';
173 Roo.get(document.head).select('style').each(function(e) {
174 st += "\n" + e.innerHTML;
177 Roo.get(document.head).select('link').each(function(node) {
178 st += node.outerHTML || new XMLSerializer().serializeToString(node);
181 return '<html><head>' + st +
182 //<style type="text/css">' +
183 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
185 ' </head><body></body></html>';
189 onRender : function(ct, position)
192 Roo.form.HtmlEditor.superclass.onRender.call(this, ct, position);
193 this.el.dom.style.border = '0 none';
194 this.el.dom.setAttribute('tabIndex', -1);
195 this.el.addClass('x-hidden');
196 if(Roo.isIE){ // fix IE 1px bogus margin
197 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
199 this.wrap = this.el.wrap({
200 cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
203 if (this.resizable) {
204 this.resizeEl = new Roo.Resizable(this.wrap, {
208 minHeight : this.height,
210 handles : this.resizable,
213 resize : function(r, w, h) {
214 _t.onResize(w,h); // -something
221 this.frameId = Roo.id();
223 this.createToolbar(this);
227 var iframe = this.wrap.createChild({
232 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
236 // console.log(iframe);
237 //this.wrap.dom.appendChild(iframe);
239 this.iframe = iframe.dom;
243 this.doc.designMode = 'on';
246 this.doc.write(this.getDocMarkup());
250 var task = { // must defer to wait for browser to be ready
252 //console.log("run task?" + this.doc.readyState);
254 if(this.doc.body || this.doc.readyState == 'complete'){
256 this.doc.designMode="on";
260 Roo.TaskMgr.stop(task);
261 this.initEditor.defer(10, this);
268 Roo.TaskMgr.start(task);
271 this.setSize(this.wrap.getSize());
274 this.resizeEl.resizeTo.defer(100, this.resizeEl,[ this.width,this.height ] );
275 // should trigger onReize..
280 onResize : function(w, h)
282 //Roo.log('resize: ' +w + ',' + h );
283 Roo.form.HtmlEditor.superclass.onResize.apply(this, arguments);
284 if(this.el && this.iframe){
285 if(typeof w == 'number'){
286 var aw = w - this.wrap.getFrameWidth('lr');
287 this.el.setWidth(this.adjustWidth('textarea', aw));
288 this.iframe.style.width = aw + 'px';
290 if(typeof h == 'number'){
292 for (var i =0; i < this.toolbars.length;i++) {
293 // fixme - ask toolbars for heights?
294 tbh += this.toolbars[i].tb.el.getHeight();
295 if (this.toolbars[i].footer) {
296 tbh += this.toolbars[i].footer.el.getHeight();
303 var ah = h - this.wrap.getFrameWidth('tb') - tbh;// this.tb.el.getHeight();
304 ah -= 5; // knock a few pixes off for look..
305 this.el.setHeight(this.adjustWidth('textarea', ah));
306 this.iframe.style.height = ah + 'px';
308 (this.doc.body || this.doc.documentElement).style.height = (ah - (this.iframePad*2)) + 'px';
315 * Toggles the editor between standard and source edit mode.
316 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
318 toggleSourceEdit : function(sourceEditMode){
320 this.sourceEditMode = sourceEditMode === true;
322 if(this.sourceEditMode){
325 this.iframe.className = 'x-hidden';
326 this.el.removeClass('x-hidden');
327 this.el.dom.removeAttribute('tabIndex');
332 this.iframe.className = '';
333 this.el.addClass('x-hidden');
334 this.el.dom.setAttribute('tabIndex', -1);
337 this.setSize(this.wrap.getSize());
338 this.fireEvent('editmodechange', this, this.sourceEditMode);
341 // private used internally
342 createLink : function(){
343 var url = prompt(this.createLinkText, this.defaultLinkValue);
344 if(url && url != 'http:/'+'/'){
345 this.relayCmd('createlink', url);
349 // private (for BoxComponent)
350 adjustSize : Roo.BoxComponent.prototype.adjustSize,
352 // private (for BoxComponent)
353 getResizeEl : function(){
357 // private (for BoxComponent)
358 getPositionEl : function(){
363 initEvents : function(){
364 this.originalValue = this.getValue();
368 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
371 markInvalid : Roo.emptyFn,
373 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
376 clearInvalid : Roo.emptyFn,
378 setValue : function(v){
379 Roo.form.HtmlEditor.superclass.setValue.call(this, v);
384 * Protected method that will not generally be called directly. If you need/want
385 * custom HTML cleanup, this is the method you should override.
386 * @param {String} html The HTML to be cleaned
387 * return {String} The cleaned HTML
389 cleanHtml : function(html){
392 if(Roo.isSafari){ // strip safari nonsense
393 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
396 if(html == ' '){
403 * Protected method that will not generally be called directly. Syncs the contents
404 * of the editor iframe with the textarea.
406 syncValue : function(){
407 if(this.initialized){
408 var bd = (this.doc.body || this.doc.documentElement);
410 var html = bd.innerHTML;
412 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
413 var m = bs.match(/text-align:(.*?);/i);
415 html = '<div style="'+m[0]+'">' + html + '</div>';
418 html = this.cleanHtml(html);
419 if(this.fireEvent('beforesync', this, html) !== false){
420 this.el.dom.value = html;
421 this.fireEvent('sync', this, html);
427 * Protected method that will not generally be called directly. Pushes the value of the textarea
428 * into the iframe editor.
430 pushValue : function(){
431 if(this.initialized){
432 var v = this.el.dom.value;
437 if(this.fireEvent('beforepush', this, v) !== false){
438 var d = (this.doc.body || this.doc.documentElement);
441 this.el.dom.value = d.innerHTML;
442 this.fireEvent('push', this, v);
448 deferFocus : function(){
449 this.focus.defer(10, this);
454 if(this.win && !this.sourceEditMode){
461 assignDocWin: function()
463 var iframe = this.iframe;
466 this.doc = iframe.contentWindow.document;
467 this.win = iframe.contentWindow;
469 if (!Roo.get(this.frameId)) {
472 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
473 this.win = Roo.get(this.frameId).dom.contentWindow;
478 initEditor : function(){
479 //console.log("INIT EDITOR");
484 this.doc.designMode="on";
486 this.doc.write(this.getDocMarkup());
489 var dbody = (this.doc.body || this.doc.documentElement);
490 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
491 // this copies styles from the containing element into thsi one..
492 // not sure why we need all of this..
493 var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
494 ss['background-attachment'] = 'fixed'; // w3c
495 dbody.bgProperties = 'fixed'; // ie
496 Roo.DomHelper.applyStyles(dbody, ss);
497 Roo.EventManager.on(this.doc, {
498 //'mousedown': this.onEditorEvent,
499 'mouseup': this.onEditorEvent,
500 'dblclick': this.onEditorEvent,
501 'click': this.onEditorEvent,
502 'keyup': this.onEditorEvent,
507 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
509 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
510 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
512 this.initialized = true;
514 this.fireEvent('initialize', this);
519 onDestroy : function(){
525 for (var i =0; i < this.toolbars.length;i++) {
526 // fixme - ask toolbars for heights?
527 this.toolbars[i].onDestroy();
530 this.wrap.dom.innerHTML = '';
536 onFirstFocus : function(){
541 this.activated = true;
542 for (var i =0; i < this.toolbars.length;i++) {
543 this.toolbars[i].onFirstFocus();
546 if(Roo.isGecko){ // prevent silly gecko errors
548 var s = this.win.getSelection();
549 if(!s.focusNode || s.focusNode.nodeType != 3){
550 var r = s.getRangeAt(0);
551 r.selectNodeContents((this.doc.body || this.doc.documentElement));
556 this.execCmd('useCSS', true);
557 this.execCmd('styleWithCSS', false);
560 this.fireEvent('activate', this);
564 adjustFont: function(btn){
565 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
566 //if(Roo.isSafari){ // safari
569 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
570 if(Roo.isSafari){ // safari
571 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
572 v = (v < 10) ? 10 : v;
573 v = (v > 48) ? 48 : v;
574 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
579 v = Math.max(1, v+adjust);
581 this.execCmd('FontSize', v );
584 onEditorEvent : function(e){
585 this.fireEvent('editorevent', this, e);
586 // this.updateToolbar();
590 insertTag : function(tg)
592 // could be a bit smarter... -> wrap the current selected tRoo..
594 this.execCmd("formatblock", tg);
598 insertText : function(txt)
602 range = this.createRange();
603 range.deleteContents();
604 //alert(Sender.getAttribute('label'));
606 range.insertNode(this.doc.createTextNode(txt));
610 relayBtnCmd : function(btn){
611 this.relayCmd(btn.cmd);
615 * Executes a Midas editor command on the editor document and performs necessary focus and
616 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
617 * @param {String} cmd The Midas command
618 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
620 relayCmd : function(cmd, value){
622 this.execCmd(cmd, value);
623 this.fireEvent('editorevent', this);
624 //this.updateToolbar();
629 * Executes a Midas editor command directly on the editor document.
630 * For visual commands, you should use {@link #relayCmd} instead.
631 * <b>This should only be called after the editor is initialized.</b>
632 * @param {String} cmd The Midas command
633 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
635 execCmd : function(cmd, value){
636 this.doc.execCommand(cmd, false, value === undefined ? null : value);
642 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
644 * @param {String} text
646 insertAtCursor : function(text){
652 var r = this.doc.selection.createRange();
659 }else if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
661 this.execCmd('InsertHTML', text);
666 mozKeyPress : function(e){
668 var c = e.getCharCode(), cmd;
671 c = String.fromCharCode(c).toLowerCase();
682 this.cleanUpPaste.defer(100, this);
698 fixKeys : function(){ // load time branching for fastest keydown performance
701 var k = e.getKey(), r;
704 r = this.doc.selection.createRange();
707 r.pasteHTML('    ');
714 r = this.doc.selection.createRange();
716 var target = r.parentElement();
717 if(!target || target.tagName.toLowerCase() != 'li'){
719 r.pasteHTML('<br />');
725 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
726 this.cleanUpPaste.defer(100, this);
732 }else if(Roo.isOpera){
738 this.execCmd('InsertHTML','    ');
741 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
742 this.cleanUpPaste.defer(100, this);
747 }else if(Roo.isSafari){
753 this.execCmd('InsertText','\t');
757 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
758 this.cleanUpPaste.defer(100, this);
766 getAllAncestors: function()
768 var p = this.getSelectedNode();
771 a.push(p); // push blank onto stack..
772 p = this.getParentElement();
776 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
780 a.push(this.doc.body);
787 getSelection : function()
790 return Roo.isIE ? this.doc.selection : this.win.getSelection();
793 getSelectedNode: function()
795 // this may only work on Gecko!!!
797 // should we cache this!!!!
802 var range = this.createRange(this.getSelection());
805 var parent = range.parentElement();
807 var testRange = range.duplicate();
808 testRange.moveToElementText(parent);
809 if (testRange.inRange(range)) {
812 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
815 parent = parent.parentElement;
820 // is ancestor a text element.
821 var ac = range.commonAncestorContainer;
822 if (ac.nodeType == 3) {
826 var ar = ac.childNodes;
829 var other_nodes = [];
830 var has_other_nodes = false;
831 for (var i=0;i<ar.length;i++) {
832 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
835 // fullly contained node.
837 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
842 // probably selected..
843 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
844 other_nodes.push(ar[i]);
848 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
853 has_other_nodes = true;
855 if (!nodes.length && other_nodes.length) {
858 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
864 createRange: function(sel)
866 // this has strange effects when using with
867 // top toolbar - not sure if it's a great idea.
868 //this.editor.contentWindow.focus();
869 if (typeof sel != "undefined") {
871 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
873 return this.doc.createRange();
876 return this.doc.createRange();
879 getParentElement: function()
883 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
885 var range = this.createRange(sel);
888 var p = range.commonAncestorContainer;
889 while (p.nodeType == 3) { // text node
900 * Range intersection.. the hard stuff...
904 * [ -- selected range --- ]
908 * if end is before start or hits it. fail.
909 * if start is after end or hits it fail.
911 * if either hits (but other is outside. - then it's not
917 // @see http://www.thismuchiknow.co.uk/?p=64.
918 rangeIntersectsNode : function(range, node)
920 var nodeRange = node.ownerDocument.createRange();
922 nodeRange.selectNode(node);
924 nodeRange.selectNodeContents(node);
927 var rangeStartRange = range.cloneRange();
928 rangeStartRange.collapse(true);
930 var rangeEndRange = range.cloneRange();
931 rangeEndRange.collapse(false);
933 var nodeStartRange = nodeRange.cloneRange();
934 nodeStartRange.collapse(true);
936 var nodeEndRange = nodeRange.cloneRange();
937 nodeEndRange.collapse(false);
939 return rangeStartRange.compareBoundaryPoints(
940 Range.START_TO_START, nodeEndRange) == -1 &&
941 rangeEndRange.compareBoundaryPoints(
942 Range.START_TO_START, nodeStartRange) == 1;
946 rangeCompareNode : function(range, node)
948 var nodeRange = node.ownerDocument.createRange();
950 nodeRange.selectNode(node);
952 nodeRange.selectNodeContents(node);
956 range.collapse(true);
958 nodeRange.collapse(true);
960 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
961 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
963 Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
965 var nodeIsBefore = ss == 1;
966 var nodeIsAfter = ee == -1;
968 if (nodeIsBefore && nodeIsAfter)
970 if (!nodeIsBefore && nodeIsAfter)
971 return 1; //right trailed.
973 if (nodeIsBefore && !nodeIsAfter)
974 return 2; // left trailed.
979 // private? - in a new class?
980 cleanUpPaste : function()
982 // cleans up the whole document..
983 // console.log('cleanuppaste');
984 this.cleanUpChildren(this.doc.body);
988 cleanUpChildren : function (n)
990 if (!n.childNodes.length) {
993 for (var i = n.childNodes.length-1; i > -1 ; i--) {
994 this.cleanUpChild(n.childNodes[i]);
1001 cleanUpChild : function (node)
1003 //console.log(node);
1004 if (node.nodeName == "#text") {
1005 // clean up silly Windows -- stuff?
1008 if (node.nodeName == "#comment") {
1009 node.parentNode.removeChild(node);
1010 // clean up silly Windows -- stuff?
1014 if (Roo.form.HtmlEditor.black.indexOf(node.tagName.toLowerCase()) > -1) {
1016 node.parentNode.removeChild(node);
1020 if (Roo.form.HtmlEditor.remove.indexOf(node.tagName.toLowerCase()) > -1) {
1021 this.cleanUpChildren(node);
1022 // inserts everything just before this node...
1023 while (node.childNodes.length) {
1024 var cn = node.childNodes[0];
1025 node.removeChild(cn);
1026 node.parentNode.insertBefore(cn, node);
1028 node.parentNode.removeChild(node);
1032 if (!node.attributes || !node.attributes.length) {
1033 this.cleanUpChildren(node);
1037 function cleanAttr(n,v)
1040 if (v.match(/^\./) || v.match(/^\//)) {
1043 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1046 Roo.log("(REMOVE)"+ node.tagName +'.' + n + '=' + v);
1047 node.removeAttribute(n);
1051 function cleanStyle(n,v)
1053 if (v.match(/expression/)) { //XSS?? should we even bother..
1054 node.removeAttribute(n);
1059 var parts = v.split(/;/);
1060 Roo.each(parts, function(p) {
1061 p = p.replace(/\s+/g,'');
1065 var l = p.split(':').shift().replace(/\s+/g,'');
1067 // only allow 'c whitelisted system attributes'
1068 if (Roo.form.HtmlEditor.cwhite.indexOf(l) < 0) {
1069 Roo.log('(REMOVE)' + node.tagName +'.' + n + ':'+l + '=' + v);
1070 node.removeAttribute(n);
1080 for (var i = node.attributes.length-1; i > -1 ; i--) {
1081 var a = node.attributes[i];
1083 if (Roo.form.HtmlEditor.ablack.indexOf(a.name.toLowerCase()) > -1) {
1084 node.removeAttribute(a.name);
1087 if (Roo.form.HtmlEditor.aclean.indexOf(a.name.toLowerCase()) > -1) {
1088 cleanAttr(a.name,a.value); // fixme..
1091 if (a.name == 'style') {
1092 cleanStyle(a.name,a.value);
1094 /// clean up MS crap..
1095 if (a.name == 'class') {
1096 if (a.value.match(/^Mso/)) {
1097 node.className = '';
1107 this.cleanUpChildren(node);
1113 // hide stuff that is not compatible
1131 * @cfg {String} fieldClass @hide
1134 * @cfg {String} focusClass @hide
1137 * @cfg {String} autoCreate @hide
1140 * @cfg {String} inputType @hide
1143 * @cfg {String} invalidClass @hide
1146 * @cfg {String} invalidText @hide
1149 * @cfg {String} msgFx @hide
1152 * @cfg {String} validateOnBlur @hide
1156 Roo.form.HtmlEditor.white = [
1157 'area', 'br', 'img', 'input', 'hr', 'wbr',
1159 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1160 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1161 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1162 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1163 'table', 'ul', 'xmp',
1165 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1168 'dir', 'menu', 'ol', 'ul', 'dl',
1174 Roo.form.HtmlEditor.black = [
1175 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1177 'base', 'basefont', 'bgsound', 'blink', 'body',
1178 'frame', 'frameset', 'head', 'html', 'ilayer',
1179 'iframe', 'layer', 'link', 'meta', 'object',
1180 'script', 'style' ,'title', 'xml' // clean later..
1182 Roo.form.HtmlEditor.clean = [
1183 'script', 'style', 'title', 'xml'
1185 Roo.form.HtmlEditor.remove = [
1190 Roo.form.HtmlEditor.ablack = [
1194 Roo.form.HtmlEditor.aclean = [
1195 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1199 Roo.form.HtmlEditor.pwhite= [
1200 'http', 'https', 'mailto'
1203 // white listed style attributes.
1204 Roo.form.HtmlEditor.cwhite= [