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.stylesheets.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()).cloneRange();
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);
1042 var remove_keep_children= Roo.form.HtmlEditor.remove.indexOf(node.tagName.toLowerCase()) > -1;
1044 // remove <a name=....> as rendering on yahoo mailer is bored with this.
1046 if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1047 remove_keep_children = true;
1050 if (remove_keep_children) {
1051 this.cleanUpChildren(node);
1052 // inserts everything just before this node...
1053 while (node.childNodes.length) {
1054 var cn = node.childNodes[0];
1055 node.removeChild(cn);
1056 node.parentNode.insertBefore(cn, node);
1058 node.parentNode.removeChild(node);
1062 if (!node.attributes || !node.attributes.length) {
1063 this.cleanUpChildren(node);
1067 function cleanAttr(n,v)
1070 if (v.match(/^\./) || v.match(/^\//)) {
1073 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1076 Roo.log("(REMOVE)"+ node.tagName +'.' + n + '=' + v);
1077 node.removeAttribute(n);
1081 function cleanStyle(n,v)
1083 if (v.match(/expression/)) { //XSS?? should we even bother..
1084 node.removeAttribute(n);
1089 var parts = v.split(/;/);
1090 Roo.each(parts, function(p) {
1091 p = p.replace(/\s+/g,'');
1095 var l = p.split(':').shift().replace(/\s+/g,'');
1097 // only allow 'c whitelisted system attributes'
1098 if (Roo.form.HtmlEditor.cwhite.indexOf(l) < 0) {
1099 Roo.log('(REMOVE)' + node.tagName +'.' + n + ':'+l + '=' + v);
1100 node.removeAttribute(n);
1110 for (var i = node.attributes.length-1; i > -1 ; i--) {
1111 var a = node.attributes[i];
1113 if (Roo.form.HtmlEditor.ablack.indexOf(a.name.toLowerCase()) > -1) {
1114 node.removeAttribute(a.name);
1117 if (Roo.form.HtmlEditor.aclean.indexOf(a.name.toLowerCase()) > -1) {
1118 cleanAttr(a.name,a.value); // fixme..
1121 if (a.name == 'style') {
1122 cleanStyle(a.name,a.value);
1124 /// clean up MS crap..
1125 if (a.name == 'class') {
1126 if (a.value.match(/^Mso/)) {
1127 node.className = '';
1137 this.cleanUpChildren(node);
1143 // hide stuff that is not compatible
1161 * @cfg {String} fieldClass @hide
1164 * @cfg {String} focusClass @hide
1167 * @cfg {String} autoCreate @hide
1170 * @cfg {String} inputType @hide
1173 * @cfg {String} invalidClass @hide
1176 * @cfg {String} invalidText @hide
1179 * @cfg {String} msgFx @hide
1182 * @cfg {String} validateOnBlur @hide
1186 Roo.form.HtmlEditor.white = [
1187 'area', 'br', 'img', 'input', 'hr', 'wbr',
1189 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1190 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1191 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1192 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1193 'table', 'ul', 'xmp',
1195 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1198 'dir', 'menu', 'ol', 'ul', 'dl',
1204 Roo.form.HtmlEditor.black = [
1205 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1207 'base', 'basefont', 'bgsound', 'blink', 'body',
1208 'frame', 'frameset', 'head', 'html', 'ilayer',
1209 'iframe', 'layer', 'link', 'meta', 'object',
1210 'script', 'style' ,'title', 'xml' // clean later..
1212 Roo.form.HtmlEditor.clean = [
1213 'script', 'style', 'title', 'xml'
1215 Roo.form.HtmlEditor.remove = [
1220 Roo.form.HtmlEditor.ablack = [
1224 Roo.form.HtmlEditor.aclean = [
1225 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1229 Roo.form.HtmlEditor.pwhite= [
1230 'http', 'https', 'mailto'
1233 // white listed style attributes.
1234 Roo.form.HtmlEditor.cwhite= [