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.
27 * This has been tested on Fireforx / Chrome.. IE may not be so great..
29 * <br><br><b>Note: The focus/blur and validation marking functionality inherited from Ext.form.Field is NOT
30 * supported by this editor.</b><br/><br/>
31 * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within
32 * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
34 Roo.form.HtmlEditor = Roo.extend(Roo.form.Field, {
36 * @cfg {Array} toolbars Array of toolbars. - defaults to just the Standard one
40 * @cfg {String} createLinkText The default text for the create link prompt
42 createLinkText : 'Please enter the URL for the link:',
44 * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
46 defaultLinkValue : 'http:/'+'/',
49 * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
54 * @cfg {Number} height (in pixels)
58 * @cfg {Number} width (in pixels)
63 * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
72 validationEvent : false,
76 sourceEditMode : false,
77 onFocus : Roo.emptyFn,
81 defaultAutoCreate : { // modified by initCompnoent..
83 style:"width:500px;height:300px;",
88 initComponent : function(){
92 * Fires when the editor is fully initialized (including the iframe)
93 * @param {HtmlEditor} this
98 * Fires when the editor is first receives the focus. Any insertion must wait
99 * until after this event.
100 * @param {HtmlEditor} this
105 * Fires before the textarea is updated with content from the editor iframe. Return false
106 * to cancel the sync.
107 * @param {HtmlEditor} this
108 * @param {String} html
113 * Fires before the iframe editor is updated with content from the textarea. Return false
114 * to cancel the push.
115 * @param {HtmlEditor} this
116 * @param {String} html
121 * Fires when the textarea is updated with content from the editor iframe.
122 * @param {HtmlEditor} this
123 * @param {String} html
128 * Fires when the iframe editor is updated with content from the textarea.
129 * @param {HtmlEditor} this
130 * @param {String} html
134 * @event editmodechange
135 * Fires when the editor switches edit modes
136 * @param {HtmlEditor} this
137 * @param {Boolean} sourceEdit True if source edit, false if standard editing.
139 editmodechange: true,
142 * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
143 * @param {HtmlEditor} this
147 this.defaultAutoCreate = {
149 style:'width: ' + this.width + 'px;height: ' + this.height + 'px;',
155 * Protected method that will not generally be called directly. It
156 * is called when the editor creates its toolbar. Override this method if you need to
157 * add custom toolbar buttons.
158 * @param {HtmlEditor} editor
160 createToolbar : function(editor){
161 if (!editor.toolbars || !editor.toolbars.length) {
162 editor.toolbars = [ new Roo.form.HtmlEditor.ToolbarStandard() ]; // can be empty?
165 for (var i =0 ; i < editor.toolbars.length;i++) {
166 editor.toolbars[i] = Roo.factory(
167 typeof(editor.toolbars[i]) == 'string' ?
168 { xtype: editor.toolbars[i]} : editor.toolbars[i],
169 Roo.form.HtmlEditor);
170 editor.toolbars[i].init(editor);
177 * Protected method that will not generally be called directly. It
178 * is called when the editor initializes the iframe with HTML contents. Override this method if you
179 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
181 getDocMarkup : function(){
184 if (this.stylesheets === false) {
186 Roo.get(document.head).select('style').each(function(node) {
187 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
190 Roo.get(document.head).select('link').each(function(node) {
191 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
194 } else if (!this.stylesheets.length) {
196 st = '<style type="text/css">' +
197 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
200 Roo.each(this.stylesheets, function(s) {
201 st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
206 st += '<style type="text/css">' +
207 'IMG { cursor: pointer } ' +
211 return '<html><head>' + st +
212 //<style type="text/css">' +
213 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
215 ' </head><body class="roo-htmleditor-body"></body></html>';
219 onRender : function(ct, position)
222 Roo.form.HtmlEditor.superclass.onRender.call(this, ct, position);
223 this.el.dom.style.border = '0 none';
224 this.el.dom.setAttribute('tabIndex', -1);
225 this.el.addClass('x-hidden');
226 if(Roo.isIE){ // fix IE 1px bogus margin
227 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
229 this.wrap = this.el.wrap({
230 cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
233 if (this.resizable) {
234 this.resizeEl = new Roo.Resizable(this.wrap, {
238 minHeight : this.height,
240 handles : this.resizable,
243 resize : function(r, w, h) {
244 _t.onResize(w,h); // -something
251 this.frameId = Roo.id();
253 this.createToolbar(this);
257 var iframe = this.wrap.createChild({
262 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
266 // console.log(iframe);
267 //this.wrap.dom.appendChild(iframe);
269 this.iframe = iframe.dom;
273 this.doc.designMode = 'on';
276 this.doc.write(this.getDocMarkup());
280 var task = { // must defer to wait for browser to be ready
282 //console.log("run task?" + this.doc.readyState);
284 if(this.doc.body || this.doc.readyState == 'complete'){
286 this.doc.designMode="on";
290 Roo.TaskMgr.stop(task);
291 this.initEditor.defer(10, this);
298 Roo.TaskMgr.start(task);
301 this.setSize(this.wrap.getSize());
304 this.resizeEl.resizeTo.defer(100, this.resizeEl,[ this.width,this.height ] );
305 // should trigger onReize..
310 onResize : function(w, h)
312 //Roo.log('resize: ' +w + ',' + h );
313 Roo.form.HtmlEditor.superclass.onResize.apply(this, arguments);
314 if(this.el && this.iframe){
315 if(typeof w == 'number'){
316 var aw = w - this.wrap.getFrameWidth('lr');
317 this.el.setWidth(this.adjustWidth('textarea', aw));
318 this.iframe.style.width = aw + 'px';
320 if(typeof h == 'number'){
322 for (var i =0; i < this.toolbars.length;i++) {
323 // fixme - ask toolbars for heights?
324 tbh += this.toolbars[i].tb.el.getHeight();
325 if (this.toolbars[i].footer) {
326 tbh += this.toolbars[i].footer.el.getHeight();
333 var ah = h - this.wrap.getFrameWidth('tb') - tbh;// this.tb.el.getHeight();
334 ah -= 5; // knock a few pixes off for look..
335 this.el.setHeight(this.adjustWidth('textarea', ah));
336 this.iframe.style.height = ah + 'px';
338 (this.doc.body || this.doc.documentElement).style.height = (ah - (this.iframePad*2)) + 'px';
345 * Toggles the editor between standard and source edit mode.
346 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
348 toggleSourceEdit : function(sourceEditMode){
350 this.sourceEditMode = sourceEditMode === true;
352 if(this.sourceEditMode){
354 // Roo.log(this.syncValue());
356 this.iframe.className = 'x-hidden';
357 this.el.removeClass('x-hidden');
358 this.el.dom.removeAttribute('tabIndex');
362 // Roo.log(this.pushValue());
364 this.iframe.className = '';
365 this.el.addClass('x-hidden');
366 this.el.dom.setAttribute('tabIndex', -1);
369 this.setSize(this.wrap.getSize());
370 this.fireEvent('editmodechange', this, this.sourceEditMode);
373 // private used internally
374 createLink : function(){
375 var url = prompt(this.createLinkText, this.defaultLinkValue);
376 if(url && url != 'http:/'+'/'){
377 this.relayCmd('createlink', url);
381 // private (for BoxComponent)
382 adjustSize : Roo.BoxComponent.prototype.adjustSize,
384 // private (for BoxComponent)
385 getResizeEl : function(){
389 // private (for BoxComponent)
390 getPositionEl : function(){
395 initEvents : function(){
396 this.originalValue = this.getValue();
400 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
403 markInvalid : Roo.emptyFn,
405 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
408 clearInvalid : Roo.emptyFn,
410 setValue : function(v){
411 Roo.form.HtmlEditor.superclass.setValue.call(this, v);
416 * Protected method that will not generally be called directly. If you need/want
417 * custom HTML cleanup, this is the method you should override.
418 * @param {String} html The HTML to be cleaned
419 * return {String} The cleaned HTML
421 cleanHtml : function(html){
424 if(Roo.isSafari){ // strip safari nonsense
425 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
428 if(html == ' '){
435 * Protected method that will not generally be called directly. Syncs the contents
436 * of the editor iframe with the textarea.
438 syncValue : function(){
439 if(this.initialized){
440 var bd = (this.doc.body || this.doc.documentElement);
441 //this.cleanUpPaste(); -- this is done else where and causes havoc..
442 var html = bd.innerHTML;
444 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
445 var m = bs.match(/text-align:(.*?);/i);
447 html = '<div style="'+m[0]+'">' + html + '</div>';
450 html = this.cleanHtml(html);
451 // fix up the special chars.. normaly like back quotes in word...
452 // however we do not want to do this with chinese..
453 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
454 var cc = b.charCodeAt();
456 (cc >= 0x4E00 && cc < 0xA000 ) ||
457 (cc >= 0x3400 && cc < 0x4E00 ) ||
458 (cc >= 0xf900 && cc < 0xfb00 )
464 if(this.fireEvent('beforesync', this, html) !== false){
465 this.el.dom.value = html;
466 this.fireEvent('sync', this, html);
472 * Protected method that will not generally be called directly. Pushes the value of the textarea
473 * into the iframe editor.
475 pushValue : function(){
476 if(this.initialized){
477 var v = this.el.dom.value;
483 if(this.fireEvent('beforepush', this, v) !== false){
484 var d = (this.doc.body || this.doc.documentElement);
487 this.el.dom.value = d.innerHTML;
488 this.fireEvent('push', this, v);
494 deferFocus : function(){
495 this.focus.defer(10, this);
500 if(this.win && !this.sourceEditMode){
507 assignDocWin: function()
509 var iframe = this.iframe;
512 this.doc = iframe.contentWindow.document;
513 this.win = iframe.contentWindow;
515 if (!Roo.get(this.frameId)) {
518 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
519 this.win = Roo.get(this.frameId).dom.contentWindow;
524 initEditor : function(){
525 //console.log("INIT EDITOR");
530 this.doc.designMode="on";
532 this.doc.write(this.getDocMarkup());
535 var dbody = (this.doc.body || this.doc.documentElement);
536 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
537 // this copies styles from the containing element into thsi one..
538 // not sure why we need all of this..
539 var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
540 ss['background-attachment'] = 'fixed'; // w3c
541 dbody.bgProperties = 'fixed'; // ie
542 Roo.DomHelper.applyStyles(dbody, ss);
543 Roo.EventManager.on(this.doc, {
544 //'mousedown': this.onEditorEvent,
545 'mouseup': this.onEditorEvent,
546 'dblclick': this.onEditorEvent,
547 'click': this.onEditorEvent,
548 'keyup': this.onEditorEvent,
553 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
555 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
556 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
558 this.initialized = true;
560 this.fireEvent('initialize', this);
565 onDestroy : function(){
571 for (var i =0; i < this.toolbars.length;i++) {
572 // fixme - ask toolbars for heights?
573 this.toolbars[i].onDestroy();
576 this.wrap.dom.innerHTML = '';
582 onFirstFocus : function(){
587 this.activated = true;
588 for (var i =0; i < this.toolbars.length;i++) {
589 this.toolbars[i].onFirstFocus();
592 if(Roo.isGecko){ // prevent silly gecko errors
594 var s = this.win.getSelection();
595 if(!s.focusNode || s.focusNode.nodeType != 3){
596 var r = s.getRangeAt(0);
597 r.selectNodeContents((this.doc.body || this.doc.documentElement));
602 this.execCmd('useCSS', true);
603 this.execCmd('styleWithCSS', false);
606 this.fireEvent('activate', this);
610 adjustFont: function(btn){
611 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
612 //if(Roo.isSafari){ // safari
615 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
616 if(Roo.isSafari){ // safari
617 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
618 v = (v < 10) ? 10 : v;
619 v = (v > 48) ? 48 : v;
620 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
625 v = Math.max(1, v+adjust);
627 this.execCmd('FontSize', v );
630 onEditorEvent : function(e){
631 this.fireEvent('editorevent', this, e);
632 // this.updateToolbar();
633 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
636 insertTag : function(tg)
638 // could be a bit smarter... -> wrap the current selected tRoo..
640 this.execCmd("formatblock", tg);
644 insertText : function(txt)
648 range = this.createRange();
649 range.deleteContents();
650 //alert(Sender.getAttribute('label'));
652 range.insertNode(this.doc.createTextNode(txt));
656 relayBtnCmd : function(btn){
657 this.relayCmd(btn.cmd);
661 * Executes a Midas editor command on the editor document and performs necessary focus and
662 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
663 * @param {String} cmd The Midas command
664 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
666 relayCmd : function(cmd, value){
668 this.execCmd(cmd, value);
669 this.fireEvent('editorevent', this);
670 //this.updateToolbar();
675 * Executes a Midas editor command directly on the editor document.
676 * For visual commands, you should use {@link #relayCmd} instead.
677 * <b>This should only be called after the editor is initialized.</b>
678 * @param {String} cmd The Midas command
679 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
681 execCmd : function(cmd, value){
682 this.doc.execCommand(cmd, false, value === undefined ? null : value);
689 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
691 * @param {String} text | dom node..
693 insertAtCursor : function(text)
704 var r = this.doc.selection.createRange();
715 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
719 // from jquery ui (MIT licenced)
723 if (win.getSelection && win.getSelection().getRangeAt) {
724 range = win.getSelection().getRangeAt(0);
725 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
726 range.insertNode(node);
727 } else if (win.document.selection && win.document.selection.createRange) {
728 // no firefox support
729 var txt = typeof(text) == 'string' ? text : text.outerHTML;
730 win.document.selection.createRange().pasteHTML(txt);
732 // no firefox support
733 var txt = typeof(text) == 'string' ? text : text.outerHTML;
734 this.execCmd('InsertHTML', txt);
743 mozKeyPress : function(e){
745 var c = e.getCharCode(), cmd;
748 c = String.fromCharCode(c).toLowerCase();
762 this.cleanUpPaste.defer(100, this);
778 fixKeys : function(){ // load time branching for fastest keydown performance
781 var k = e.getKey(), r;
784 r = this.doc.selection.createRange();
787 r.pasteHTML('    ');
794 r = this.doc.selection.createRange();
796 var target = r.parentElement();
797 if(!target || target.tagName.toLowerCase() != 'li'){
799 r.pasteHTML('<br />');
805 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
806 this.cleanUpPaste.defer(100, this);
812 }else if(Roo.isOpera){
818 this.execCmd('InsertHTML','    ');
821 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
822 this.cleanUpPaste.defer(100, this);
827 }else if(Roo.isSafari){
833 this.execCmd('InsertText','\t');
837 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
838 this.cleanUpPaste.defer(100, this);
846 getAllAncestors: function()
848 var p = this.getSelectedNode();
851 a.push(p); // push blank onto stack..
852 p = this.getParentElement();
856 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
860 a.push(this.doc.body);
867 getSelection : function()
870 return Roo.isIE ? this.doc.selection : this.win.getSelection();
873 getSelectedNode: function()
875 // this may only work on Gecko!!!
877 // should we cache this!!!!
882 var range = this.createRange(this.getSelection()).cloneRange();
885 var parent = range.parentElement();
887 var testRange = range.duplicate();
888 testRange.moveToElementText(parent);
889 if (testRange.inRange(range)) {
892 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
895 parent = parent.parentElement;
900 // is ancestor a text element.
901 var ac = range.commonAncestorContainer;
902 if (ac.nodeType == 3) {
906 var ar = ac.childNodes;
909 var other_nodes = [];
910 var has_other_nodes = false;
911 for (var i=0;i<ar.length;i++) {
912 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
915 // fullly contained node.
917 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
922 // probably selected..
923 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
924 other_nodes.push(ar[i]);
928 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
933 has_other_nodes = true;
935 if (!nodes.length && other_nodes.length) {
938 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
944 createRange: function(sel)
946 // this has strange effects when using with
947 // top toolbar - not sure if it's a great idea.
948 //this.editor.contentWindow.focus();
949 if (typeof sel != "undefined") {
951 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
953 return this.doc.createRange();
956 return this.doc.createRange();
959 getParentElement: function()
963 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
965 var range = this.createRange(sel);
968 var p = range.commonAncestorContainer;
969 while (p.nodeType == 3) { // text node
980 * Range intersection.. the hard stuff...
984 * [ -- selected range --- ]
988 * if end is before start or hits it. fail.
989 * if start is after end or hits it fail.
991 * if either hits (but other is outside. - then it's not
997 // @see http://www.thismuchiknow.co.uk/?p=64.
998 rangeIntersectsNode : function(range, node)
1000 var nodeRange = node.ownerDocument.createRange();
1002 nodeRange.selectNode(node);
1004 nodeRange.selectNodeContents(node);
1007 var rangeStartRange = range.cloneRange();
1008 rangeStartRange.collapse(true);
1010 var rangeEndRange = range.cloneRange();
1011 rangeEndRange.collapse(false);
1013 var nodeStartRange = nodeRange.cloneRange();
1014 nodeStartRange.collapse(true);
1016 var nodeEndRange = nodeRange.cloneRange();
1017 nodeEndRange.collapse(false);
1019 return rangeStartRange.compareBoundaryPoints(
1020 Range.START_TO_START, nodeEndRange) == -1 &&
1021 rangeEndRange.compareBoundaryPoints(
1022 Range.START_TO_START, nodeStartRange) == 1;
1026 rangeCompareNode : function(range, node)
1028 var nodeRange = node.ownerDocument.createRange();
1030 nodeRange.selectNode(node);
1032 nodeRange.selectNodeContents(node);
1036 range.collapse(true);
1038 nodeRange.collapse(true);
1040 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1041 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
1043 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1045 var nodeIsBefore = ss == 1;
1046 var nodeIsAfter = ee == -1;
1048 if (nodeIsBefore && nodeIsAfter)
1050 if (!nodeIsBefore && nodeIsAfter)
1051 return 1; //right trailed.
1053 if (nodeIsBefore && !nodeIsAfter)
1054 return 2; // left trailed.
1059 // private? - in a new class?
1060 cleanUpPaste : function()
1062 // cleans up the whole document..
1063 Roo.log('cleanuppaste');
1064 this.cleanUpChildren(this.doc.body);
1065 var clean = this.cleanWordChars(this.doc.body.innerHTML);
1066 if (clean != this.doc.body.innerHTML) {
1067 this.doc.body.innerHTML = clean;
1072 cleanWordChars : function(input) {// change the chars to hex code
1073 var he = Roo.form.HtmlEditor;
1076 Roo.each(he.swapCodes, function(sw) {
1077 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1079 output = output.replace(swapper, sw[1]);
1086 cleanUpChildren : function (n)
1088 if (!n.childNodes.length) {
1091 for (var i = n.childNodes.length-1; i > -1 ; i--) {
1092 this.cleanUpChild(n.childNodes[i]);
1099 cleanUpChild : function (node)
1102 //console.log(node);
1103 if (node.nodeName == "#text") {
1104 // clean up silly Windows -- stuff?
1107 if (node.nodeName == "#comment") {
1108 node.parentNode.removeChild(node);
1109 // clean up silly Windows -- stuff?
1113 if (Roo.form.HtmlEditor.black.indexOf(node.tagName.toLowerCase()) > -1) {
1115 node.parentNode.removeChild(node);
1120 var remove_keep_children= Roo.form.HtmlEditor.remove.indexOf(node.tagName.toLowerCase()) > -1;
1122 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1123 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1125 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1126 // remove_keep_children = true;
1129 if (remove_keep_children) {
1130 this.cleanUpChildren(node);
1131 // inserts everything just before this node...
1132 while (node.childNodes.length) {
1133 var cn = node.childNodes[0];
1134 node.removeChild(cn);
1135 node.parentNode.insertBefore(cn, node);
1137 node.parentNode.removeChild(node);
1141 if (!node.attributes || !node.attributes.length) {
1142 this.cleanUpChildren(node);
1146 function cleanAttr(n,v)
1149 if (v.match(/^\./) || v.match(/^\//)) {
1152 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1155 if (v.match(/^#/)) {
1158 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1159 node.removeAttribute(n);
1163 function cleanStyle(n,v)
1165 if (v.match(/expression/)) { //XSS?? should we even bother..
1166 node.removeAttribute(n);
1169 var cwhite = typeof(ed.cwhite) == 'undefined' ? Roo.form.HtmlEditor.cwhite : ed.cwhite;
1170 var cblack = typeof(ed.cblack) == 'undefined' ? Roo.form.HtmlEditor.cblack : ed.cblack;
1173 var parts = v.split(/;/);
1176 Roo.each(parts, function(p) {
1177 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1181 var l = p.split(':').shift().replace(/\s+/g,'');
1182 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1185 if ( cblack.indexOf(l) > -1) {
1186 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1187 //node.removeAttribute(n);
1191 // only allow 'c whitelisted system attributes'
1192 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1193 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1194 //node.removeAttribute(n);
1205 node.setAttribute(n, clean.join(';'));
1207 node.removeAttribute(n);
1213 for (var i = node.attributes.length-1; i > -1 ; i--) {
1214 var a = node.attributes[i];
1217 if (a.name.toLowerCase().substr(0,2)=='on') {
1218 node.removeAttribute(a.name);
1221 if (Roo.form.HtmlEditor.ablack.indexOf(a.name.toLowerCase()) > -1) {
1222 node.removeAttribute(a.name);
1225 if (Roo.form.HtmlEditor.aclean.indexOf(a.name.toLowerCase()) > -1) {
1226 cleanAttr(a.name,a.value); // fixme..
1229 if (a.name == 'style') {
1230 cleanStyle(a.name,a.value);
1233 /// clean up MS crap..
1234 // tecnically this should be a list of valid class'es..
1237 if (a.name == 'class') {
1238 if (a.value.match(/^Mso/)) {
1239 node.className = '';
1242 if (a.value.match(/body/)) {
1243 node.className = '';
1254 this.cleanUpChildren(node);
1260 // hide stuff that is not compatible
1278 * @cfg {String} fieldClass @hide
1281 * @cfg {String} focusClass @hide
1284 * @cfg {String} autoCreate @hide
1287 * @cfg {String} inputType @hide
1290 * @cfg {String} invalidClass @hide
1293 * @cfg {String} invalidText @hide
1296 * @cfg {String} msgFx @hide
1299 * @cfg {String} validateOnBlur @hide
1303 Roo.form.HtmlEditor.white = [
1304 'area', 'br', 'img', 'input', 'hr', 'wbr',
1306 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1307 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1308 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1309 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1310 'table', 'ul', 'xmp',
1312 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1315 'dir', 'menu', 'ol', 'ul', 'dl',
1321 Roo.form.HtmlEditor.black = [
1322 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1324 'base', 'basefont', 'bgsound', 'blink', 'body',
1325 'frame', 'frameset', 'head', 'html', 'ilayer',
1326 'iframe', 'layer', 'link', 'meta', 'object',
1327 'script', 'style' ,'title', 'xml' // clean later..
1329 Roo.form.HtmlEditor.clean = [
1330 'script', 'style', 'title', 'xml'
1332 Roo.form.HtmlEditor.remove = [
1337 Roo.form.HtmlEditor.ablack = [
1341 Roo.form.HtmlEditor.aclean = [
1342 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1346 Roo.form.HtmlEditor.pwhite= [
1347 'http', 'https', 'mailto'
1350 // white listed style attributes.
1351 Roo.form.HtmlEditor.cwhite= [
1352 // 'text-align', /// default is to allow most things..
1358 // black listed style attributes.
1359 Roo.form.HtmlEditor.cblack= [
1360 // 'font-size' -- this can be set by the project
1364 Roo.form.HtmlEditor.swapCodes =[