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(editor.toolbars[i], Roo.form.HtmlEditor);
167 editor.toolbars[i].init(editor);
174 * Protected method that will not generally be called directly. It
175 * is called when the editor initializes the iframe with HTML contents. Override this method if you
176 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
178 getDocMarkup : function(){
181 if (this.stylesheets === false) {
183 Roo.get(document.head).select('style').each(function(node) {
184 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
187 Roo.get(document.head).select('link').each(function(node) {
188 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
191 } else if (!this.stylesheets.length) {
193 st = '<style type="text/css">' +
194 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
197 Roo.each(this.stylesheets, function(s) {
198 st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
203 st += '<style type="text/css">' +
204 'IMG { cursor: pointer } ' +
208 return '<html><head>' + st +
209 //<style type="text/css">' +
210 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
212 ' </head><body class="roo-htmleditor-body"></body></html>';
216 onRender : function(ct, position)
219 Roo.form.HtmlEditor.superclass.onRender.call(this, ct, position);
220 this.el.dom.style.border = '0 none';
221 this.el.dom.setAttribute('tabIndex', -1);
222 this.el.addClass('x-hidden');
223 if(Roo.isIE){ // fix IE 1px bogus margin
224 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
226 this.wrap = this.el.wrap({
227 cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
230 if (this.resizable) {
231 this.resizeEl = new Roo.Resizable(this.wrap, {
235 minHeight : this.height,
237 handles : this.resizable,
240 resize : function(r, w, h) {
241 _t.onResize(w,h); // -something
248 this.frameId = Roo.id();
250 this.createToolbar(this);
254 var iframe = this.wrap.createChild({
259 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
263 // console.log(iframe);
264 //this.wrap.dom.appendChild(iframe);
266 this.iframe = iframe.dom;
270 this.doc.designMode = 'on';
273 this.doc.write(this.getDocMarkup());
277 var task = { // must defer to wait for browser to be ready
279 //console.log("run task?" + this.doc.readyState);
281 if(this.doc.body || this.doc.readyState == 'complete'){
283 this.doc.designMode="on";
287 Roo.TaskMgr.stop(task);
288 this.initEditor.defer(10, this);
295 Roo.TaskMgr.start(task);
298 this.setSize(this.wrap.getSize());
301 this.resizeEl.resizeTo.defer(100, this.resizeEl,[ this.width,this.height ] );
302 // should trigger onReize..
307 onResize : function(w, h)
309 //Roo.log('resize: ' +w + ',' + h );
310 Roo.form.HtmlEditor.superclass.onResize.apply(this, arguments);
311 if(this.el && this.iframe){
312 if(typeof w == 'number'){
313 var aw = w - this.wrap.getFrameWidth('lr');
314 this.el.setWidth(this.adjustWidth('textarea', aw));
315 this.iframe.style.width = aw + 'px';
317 if(typeof h == 'number'){
319 for (var i =0; i < this.toolbars.length;i++) {
320 // fixme - ask toolbars for heights?
321 tbh += this.toolbars[i].tb.el.getHeight();
322 if (this.toolbars[i].footer) {
323 tbh += this.toolbars[i].footer.el.getHeight();
330 var ah = h - this.wrap.getFrameWidth('tb') - tbh;// this.tb.el.getHeight();
331 ah -= 5; // knock a few pixes off for look..
332 this.el.setHeight(this.adjustWidth('textarea', ah));
333 this.iframe.style.height = ah + 'px';
335 (this.doc.body || this.doc.documentElement).style.height = (ah - (this.iframePad*2)) + 'px';
342 * Toggles the editor between standard and source edit mode.
343 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
345 toggleSourceEdit : function(sourceEditMode){
347 this.sourceEditMode = sourceEditMode === true;
349 if(this.sourceEditMode){
351 // Roo.log(this.syncValue());
353 this.iframe.className = 'x-hidden';
354 this.el.removeClass('x-hidden');
355 this.el.dom.removeAttribute('tabIndex');
359 // Roo.log(this.pushValue());
361 this.iframe.className = '';
362 this.el.addClass('x-hidden');
363 this.el.dom.setAttribute('tabIndex', -1);
366 this.setSize(this.wrap.getSize());
367 this.fireEvent('editmodechange', this, this.sourceEditMode);
370 // private used internally
371 createLink : function(){
372 var url = prompt(this.createLinkText, this.defaultLinkValue);
373 if(url && url != 'http:/'+'/'){
374 this.relayCmd('createlink', url);
378 // private (for BoxComponent)
379 adjustSize : Roo.BoxComponent.prototype.adjustSize,
381 // private (for BoxComponent)
382 getResizeEl : function(){
386 // private (for BoxComponent)
387 getPositionEl : function(){
392 initEvents : function(){
393 this.originalValue = this.getValue();
397 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
400 markInvalid : Roo.emptyFn,
402 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
405 clearInvalid : Roo.emptyFn,
407 setValue : function(v){
408 Roo.form.HtmlEditor.superclass.setValue.call(this, v);
413 * Protected method that will not generally be called directly. If you need/want
414 * custom HTML cleanup, this is the method you should override.
415 * @param {String} html The HTML to be cleaned
416 * return {String} The cleaned HTML
418 cleanHtml : function(html){
421 if(Roo.isSafari){ // strip safari nonsense
422 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
425 if(html == ' '){
432 * Protected method that will not generally be called directly. Syncs the contents
433 * of the editor iframe with the textarea.
435 syncValue : function(){
436 if(this.initialized){
437 var bd = (this.doc.body || this.doc.documentElement);
438 //this.cleanUpPaste(); -- this is done else where and causes havoc..
439 var html = bd.innerHTML;
441 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
442 var m = bs.match(/text-align:(.*?);/i);
444 html = '<div style="'+m[0]+'">' + html + '</div>';
447 html = this.cleanHtml(html);
448 // fix up the special chars.. normaly like back quotes in word...
449 // however we do not want to do this with chinese..
450 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
451 var cc = b.charCodeAt();
453 (cc >= 0x4E00 && cc < 0xA000 ) ||
454 (cc >= 0x3400 && cc < 0x4E00 ) ||
455 (cc >= 0xf900 && cc < 0xfb00 )
461 if(this.fireEvent('beforesync', this, html) !== false){
462 this.el.dom.value = html;
463 this.fireEvent('sync', this, html);
469 * Protected method that will not generally be called directly. Pushes the value of the textarea
470 * into the iframe editor.
472 pushValue : function(){
473 if(this.initialized){
474 var v = this.el.dom.value;
480 if(this.fireEvent('beforepush', this, v) !== false){
481 var d = (this.doc.body || this.doc.documentElement);
485 this.el.dom.value = d.innerHTML;
486 this.fireEvent('push', this, v);
492 deferFocus : function(){
493 this.focus.defer(10, this);
498 if(this.win && !this.sourceEditMode){
505 assignDocWin: function()
507 var iframe = this.iframe;
510 this.doc = iframe.contentWindow.document;
511 this.win = iframe.contentWindow;
513 if (!Roo.get(this.frameId)) {
516 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
517 this.win = Roo.get(this.frameId).dom.contentWindow;
522 initEditor : function(){
523 //console.log("INIT EDITOR");
528 this.doc.designMode="on";
530 this.doc.write(this.getDocMarkup());
533 var dbody = (this.doc.body || this.doc.documentElement);
534 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
535 // this copies styles from the containing element into thsi one..
536 // not sure why we need all of this..
537 var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
538 ss['background-attachment'] = 'fixed'; // w3c
539 dbody.bgProperties = 'fixed'; // ie
540 Roo.DomHelper.applyStyles(dbody, ss);
541 Roo.EventManager.on(this.doc, {
542 //'mousedown': this.onEditorEvent,
543 'mouseup': this.onEditorEvent,
544 'dblclick': this.onEditorEvent,
545 'click': this.onEditorEvent,
546 'keyup': this.onEditorEvent,
551 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
553 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
554 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
556 this.initialized = true;
558 this.fireEvent('initialize', this);
563 onDestroy : function(){
569 for (var i =0; i < this.toolbars.length;i++) {
570 // fixme - ask toolbars for heights?
571 this.toolbars[i].onDestroy();
574 this.wrap.dom.innerHTML = '';
580 onFirstFocus : function(){
585 this.activated = true;
586 for (var i =0; i < this.toolbars.length;i++) {
587 this.toolbars[i].onFirstFocus();
590 if(Roo.isGecko){ // prevent silly gecko errors
592 var s = this.win.getSelection();
593 if(!s.focusNode || s.focusNode.nodeType != 3){
594 var r = s.getRangeAt(0);
595 r.selectNodeContents((this.doc.body || this.doc.documentElement));
600 this.execCmd('useCSS', true);
601 this.execCmd('styleWithCSS', false);
604 this.fireEvent('activate', this);
608 adjustFont: function(btn){
609 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
610 //if(Roo.isSafari){ // safari
613 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
614 if(Roo.isSafari){ // safari
615 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
616 v = (v < 10) ? 10 : v;
617 v = (v > 48) ? 48 : v;
618 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
623 v = Math.max(1, v+adjust);
625 this.execCmd('FontSize', v );
628 onEditorEvent : function(e){
629 this.fireEvent('editorevent', this, e);
630 // this.updateToolbar();
631 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
634 insertTag : function(tg)
636 // could be a bit smarter... -> wrap the current selected tRoo..
638 this.execCmd("formatblock", tg);
642 insertText : function(txt)
646 range = this.createRange();
647 range.deleteContents();
648 //alert(Sender.getAttribute('label'));
650 range.insertNode(this.doc.createTextNode(txt));
654 relayBtnCmd : function(btn){
655 this.relayCmd(btn.cmd);
659 * Executes a Midas editor command on the editor document and performs necessary focus and
660 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
661 * @param {String} cmd The Midas command
662 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
664 relayCmd : function(cmd, value){
666 this.execCmd(cmd, value);
667 this.fireEvent('editorevent', this);
668 //this.updateToolbar();
673 * Executes a Midas editor command directly on the editor document.
674 * For visual commands, you should use {@link #relayCmd} instead.
675 * <b>This should only be called after the editor is initialized.</b>
676 * @param {String} cmd The Midas command
677 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
679 execCmd : function(cmd, value){
680 this.doc.execCommand(cmd, false, value === undefined ? null : value);
687 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
689 * @param {String} text | dom node..
691 insertAtCursor : function(text)
702 var r = this.doc.selection.createRange();
713 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
717 // from jquery ui (MIT licenced)
721 if (win.getSelection && win.getSelection().getRangeAt) {
722 range = win.getSelection().getRangeAt(0);
723 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
724 range.insertNode(node);
725 } else if (win.document.selection && win.document.selection.createRange) {
726 // no firefox support
727 var txt = typeof(text) == 'string' ? text : text.outerHTML;
728 win.document.selection.createRange().pasteHTML(txt);
730 // no firefox support
731 var txt = typeof(text) == 'string' ? text : text.outerHTML;
732 this.execCmd('InsertHTML', txt);
741 mozKeyPress : function(e){
743 var c = e.getCharCode(), cmd;
746 c = String.fromCharCode(c).toLowerCase();
760 this.cleanUpPaste.defer(100, this);
776 fixKeys : function(){ // load time branching for fastest keydown performance
779 var k = e.getKey(), r;
782 r = this.doc.selection.createRange();
785 r.pasteHTML('    ');
792 r = this.doc.selection.createRange();
794 var target = r.parentElement();
795 if(!target || target.tagName.toLowerCase() != 'li'){
797 r.pasteHTML('<br />');
803 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
804 this.cleanUpPaste.defer(100, this);
810 }else if(Roo.isOpera){
816 this.execCmd('InsertHTML','    ');
819 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
820 this.cleanUpPaste.defer(100, this);
825 }else if(Roo.isSafari){
831 this.execCmd('InsertText','\t');
835 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
836 this.cleanUpPaste.defer(100, this);
844 getAllAncestors: function()
846 var p = this.getSelectedNode();
849 a.push(p); // push blank onto stack..
850 p = this.getParentElement();
854 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
858 a.push(this.doc.body);
865 getSelection : function()
868 return Roo.isIE ? this.doc.selection : this.win.getSelection();
871 getSelectedNode: function()
873 // this may only work on Gecko!!!
875 // should we cache this!!!!
880 var range = this.createRange(this.getSelection()).cloneRange();
883 var parent = range.parentElement();
885 var testRange = range.duplicate();
886 testRange.moveToElementText(parent);
887 if (testRange.inRange(range)) {
890 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
893 parent = parent.parentElement;
898 // is ancestor a text element.
899 var ac = range.commonAncestorContainer;
900 if (ac.nodeType == 3) {
904 var ar = ac.childNodes;
907 var other_nodes = [];
908 var has_other_nodes = false;
909 for (var i=0;i<ar.length;i++) {
910 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
913 // fullly contained node.
915 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
920 // probably selected..
921 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
922 other_nodes.push(ar[i]);
926 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
931 has_other_nodes = true;
933 if (!nodes.length && other_nodes.length) {
936 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
942 createRange: function(sel)
944 // this has strange effects when using with
945 // top toolbar - not sure if it's a great idea.
946 //this.editor.contentWindow.focus();
947 if (typeof sel != "undefined") {
949 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
951 return this.doc.createRange();
954 return this.doc.createRange();
957 getParentElement: function()
961 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
963 var range = this.createRange(sel);
966 var p = range.commonAncestorContainer;
967 while (p.nodeType == 3) { // text node
978 * Range intersection.. the hard stuff...
982 * [ -- selected range --- ]
986 * if end is before start or hits it. fail.
987 * if start is after end or hits it fail.
989 * if either hits (but other is outside. - then it's not
995 // @see http://www.thismuchiknow.co.uk/?p=64.
996 rangeIntersectsNode : function(range, node)
998 var nodeRange = node.ownerDocument.createRange();
1000 nodeRange.selectNode(node);
1002 nodeRange.selectNodeContents(node);
1005 var rangeStartRange = range.cloneRange();
1006 rangeStartRange.collapse(true);
1008 var rangeEndRange = range.cloneRange();
1009 rangeEndRange.collapse(false);
1011 var nodeStartRange = nodeRange.cloneRange();
1012 nodeStartRange.collapse(true);
1014 var nodeEndRange = nodeRange.cloneRange();
1015 nodeEndRange.collapse(false);
1017 return rangeStartRange.compareBoundaryPoints(
1018 Range.START_TO_START, nodeEndRange) == -1 &&
1019 rangeEndRange.compareBoundaryPoints(
1020 Range.START_TO_START, nodeStartRange) == 1;
1024 rangeCompareNode : function(range, node)
1026 var nodeRange = node.ownerDocument.createRange();
1028 nodeRange.selectNode(node);
1030 nodeRange.selectNodeContents(node);
1034 range.collapse(true);
1036 nodeRange.collapse(true);
1038 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1039 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
1041 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1043 var nodeIsBefore = ss == 1;
1044 var nodeIsAfter = ee == -1;
1046 if (nodeIsBefore && nodeIsAfter)
1048 if (!nodeIsBefore && nodeIsAfter)
1049 return 1; //right trailed.
1051 if (nodeIsBefore && !nodeIsAfter)
1052 return 2; // left trailed.
1057 // private? - in a new class?
1058 cleanUpPaste : function()
1060 // cleans up the whole document..
1061 Roo.log('cleanuppaste');
1062 this.cleanUpChildren(this.doc.body);
1063 var clean = this.cleanWordChars(this.doc.body.innerHTML);
1064 if (clean != this.doc.body.innerHTML) {
1065 this.doc.body.innerHTML = clean;
1070 cleanWordChars : function(input) {
1071 var he = Roo.form.HtmlEditor;
1072 Roo.log('inputt........');
1076 Roo.each(he.swapCodes, function(sw) {
1078 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1080 output = output.replace(swapper, sw[1]);
1082 Roo.log('output........');
1088 cleanUpChildren : function (n)
1090 if (!n.childNodes.length) {
1093 for (var i = n.childNodes.length-1; i > -1 ; i--) {
1094 this.cleanUpChild(n.childNodes[i]);
1101 cleanUpChild : function (node)
1106 //console.log(node);
1107 if (node.nodeName == "#text") {
1108 // clean up silly Windows -- stuff?
1111 if (node.nodeName == "#comment") {
1112 node.parentNode.removeChild(node);
1113 // clean up silly Windows -- stuff?
1117 if (Roo.form.HtmlEditor.black.indexOf(node.tagName.toLowerCase()) > -1) {
1119 node.parentNode.removeChild(node);
1124 var remove_keep_children= Roo.form.HtmlEditor.remove.indexOf(node.tagName.toLowerCase()) > -1;
1126 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1127 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1129 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1130 // remove_keep_children = true;
1133 if (remove_keep_children) {
1134 this.cleanUpChildren(node);
1135 // inserts everything just before this node...
1136 while (node.childNodes.length) {
1137 var cn = node.childNodes[0];
1138 node.removeChild(cn);
1139 node.parentNode.insertBefore(cn, node);
1141 node.parentNode.removeChild(node);
1145 if (!node.attributes || !node.attributes.length) {
1146 this.cleanUpChildren(node);
1150 function cleanAttr(n,v)
1153 if (v.match(/^\./) || v.match(/^\//)) {
1156 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1159 if (v.match(/^#/)) {
1162 Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1163 node.removeAttribute(n);
1167 function cleanStyle(n,v)
1169 if (v.match(/expression/)) { //XSS?? should we even bother..
1170 node.removeAttribute(n);
1173 var cwhite = typeof(ed.cwhite) == 'undefined' ? Roo.form.HtmlEditor.cwhite : ed.cwhite;
1175 var parts = v.split(/;/);
1177 Roo.each(parts, function(p) {
1178 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1182 var l = p.split(':').shift().replace(/\s+/g,'');
1183 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1185 // only allow 'c whitelisted system attributes'
1186 if ( cwhite.indexOf(l) < 0) {
1187 Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1188 //node.removeAttribute(n);
1195 node.setAttribute(n, clean.join(';'));
1197 node.removeAttribute(n);
1203 for (var i = node.attributes.length-1; i > -1 ; i--) {
1204 var a = node.attributes[i];
1207 if (a.name.toLowerCase().substr(0,2)=='on') {
1208 node.removeAttribute(a.name);
1211 if (Roo.form.HtmlEditor.ablack.indexOf(a.name.toLowerCase()) > -1) {
1212 node.removeAttribute(a.name);
1215 if (Roo.form.HtmlEditor.aclean.indexOf(a.name.toLowerCase()) > -1) {
1216 cleanAttr(a.name,a.value); // fixme..
1219 if (a.name == 'style') {
1220 cleanStyle(a.name,a.value);
1223 /// clean up MS crap..
1224 // tecnically this should be a list of valid class'es..
1227 if (a.name == 'class') {
1228 if (a.value.match(/^Mso/)) {
1229 node.className = '';
1232 if (a.value.match(/body/)) {
1233 node.className = '';
1244 this.cleanUpChildren(node);
1250 // hide stuff that is not compatible
1268 * @cfg {String} fieldClass @hide
1271 * @cfg {String} focusClass @hide
1274 * @cfg {String} autoCreate @hide
1277 * @cfg {String} inputType @hide
1280 * @cfg {String} invalidClass @hide
1283 * @cfg {String} invalidText @hide
1286 * @cfg {String} msgFx @hide
1289 * @cfg {String} validateOnBlur @hide
1293 Roo.form.HtmlEditor.white = [
1294 'area', 'br', 'img', 'input', 'hr', 'wbr',
1296 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1297 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1298 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1299 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1300 'table', 'ul', 'xmp',
1302 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1305 'dir', 'menu', 'ol', 'ul', 'dl',
1311 Roo.form.HtmlEditor.black = [
1312 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1314 'base', 'basefont', 'bgsound', 'blink', 'body',
1315 'frame', 'frameset', 'head', 'html', 'ilayer',
1316 'iframe', 'layer', 'link', 'meta', 'object',
1317 'script', 'style' ,'title', 'xml' // clean later..
1319 Roo.form.HtmlEditor.clean = [
1320 'script', 'style', 'title', 'xml'
1322 Roo.form.HtmlEditor.remove = [
1327 Roo.form.HtmlEditor.ablack = [
1331 Roo.form.HtmlEditor.aclean = [
1332 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1336 Roo.form.HtmlEditor.pwhite= [
1337 'http', 'https', 'mailto'
1340 // white listed style attributes.
1341 Roo.form.HtmlEditor.cwhite= [
1347 Roo.form.HtmlEditor.swapCodes =[