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);
484 this.el.dom.value = d.innerHTML;
485 this.fireEvent('push', this, v);
491 deferFocus : function(){
492 this.focus.defer(10, this);
497 if(this.win && !this.sourceEditMode){
504 assignDocWin: function()
506 var iframe = this.iframe;
509 this.doc = iframe.contentWindow.document;
510 this.win = iframe.contentWindow;
512 if (!Roo.get(this.frameId)) {
515 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
516 this.win = Roo.get(this.frameId).dom.contentWindow;
521 initEditor : function(){
522 //console.log("INIT EDITOR");
527 this.doc.designMode="on";
529 this.doc.write(this.getDocMarkup());
532 var dbody = (this.doc.body || this.doc.documentElement);
533 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
534 // this copies styles from the containing element into thsi one..
535 // not sure why we need all of this..
536 var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
537 ss['background-attachment'] = 'fixed'; // w3c
538 dbody.bgProperties = 'fixed'; // ie
539 Roo.DomHelper.applyStyles(dbody, ss);
540 Roo.EventManager.on(this.doc, {
541 //'mousedown': this.onEditorEvent,
542 'mouseup': this.onEditorEvent,
543 'dblclick': this.onEditorEvent,
544 'click': this.onEditorEvent,
545 'keyup': this.onEditorEvent,
550 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
552 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
553 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
555 this.initialized = true;
557 this.fireEvent('initialize', this);
562 onDestroy : function(){
568 for (var i =0; i < this.toolbars.length;i++) {
569 // fixme - ask toolbars for heights?
570 this.toolbars[i].onDestroy();
573 this.wrap.dom.innerHTML = '';
579 onFirstFocus : function(){
584 this.activated = true;
585 for (var i =0; i < this.toolbars.length;i++) {
586 this.toolbars[i].onFirstFocus();
589 if(Roo.isGecko){ // prevent silly gecko errors
591 var s = this.win.getSelection();
592 if(!s.focusNode || s.focusNode.nodeType != 3){
593 var r = s.getRangeAt(0);
594 r.selectNodeContents((this.doc.body || this.doc.documentElement));
599 this.execCmd('useCSS', true);
600 this.execCmd('styleWithCSS', false);
603 this.fireEvent('activate', this);
607 adjustFont: function(btn){
608 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
609 //if(Roo.isSafari){ // safari
612 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
613 if(Roo.isSafari){ // safari
614 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
615 v = (v < 10) ? 10 : v;
616 v = (v > 48) ? 48 : v;
617 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
622 v = Math.max(1, v+adjust);
624 this.execCmd('FontSize', v );
627 onEditorEvent : function(e){
628 this.fireEvent('editorevent', this, e);
629 // this.updateToolbar();
630 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
633 insertTag : function(tg)
635 // could be a bit smarter... -> wrap the current selected tRoo..
637 this.execCmd("formatblock", tg);
641 insertText : function(txt)
645 range = this.createRange();
646 range.deleteContents();
647 //alert(Sender.getAttribute('label'));
649 range.insertNode(this.doc.createTextNode(txt));
653 relayBtnCmd : function(btn){
654 this.relayCmd(btn.cmd);
658 * Executes a Midas editor command on the editor document and performs necessary focus and
659 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
660 * @param {String} cmd The Midas command
661 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
663 relayCmd : function(cmd, value){
665 this.execCmd(cmd, value);
666 this.fireEvent('editorevent', this);
667 //this.updateToolbar();
672 * Executes a Midas editor command directly on the editor document.
673 * For visual commands, you should use {@link #relayCmd} instead.
674 * <b>This should only be called after the editor is initialized.</b>
675 * @param {String} cmd The Midas command
676 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
678 execCmd : function(cmd, value){
679 this.doc.execCommand(cmd, false, value === undefined ? null : value);
686 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
688 * @param {String} text | dom node..
690 insertAtCursor : function(text)
701 var r = this.doc.selection.createRange();
712 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
716 // from jquery ui (MIT licenced)
720 if (win.getSelection && win.getSelection().getRangeAt) {
721 range = win.getSelection().getRangeAt(0);
722 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
723 range.insertNode(node);
724 } else if (win.document.selection && win.document.selection.createRange) {
725 // no firefox support
726 var txt = typeof(text) == 'string' ? text : text.outerHTML;
727 win.document.selection.createRange().pasteHTML(txt);
729 // no firefox support
730 var txt = typeof(text) == 'string' ? text : text.outerHTML;
731 this.execCmd('InsertHTML', txt);
740 mozKeyPress : function(e){
742 var c = e.getCharCode(), cmd;
745 c = String.fromCharCode(c).toLowerCase();
759 this.cleanUpPaste.defer(100, this);
775 fixKeys : function(){ // load time branching for fastest keydown performance
778 var k = e.getKey(), r;
781 r = this.doc.selection.createRange();
784 r.pasteHTML('    ');
791 r = this.doc.selection.createRange();
793 var target = r.parentElement();
794 if(!target || target.tagName.toLowerCase() != 'li'){
796 r.pasteHTML('<br />');
802 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
803 this.cleanUpPaste.defer(100, this);
809 }else if(Roo.isOpera){
815 this.execCmd('InsertHTML','    ');
818 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
819 this.cleanUpPaste.defer(100, this);
824 }else if(Roo.isSafari){
830 this.execCmd('InsertText','\t');
834 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
835 this.cleanUpPaste.defer(100, this);
843 getAllAncestors: function()
845 var p = this.getSelectedNode();
848 a.push(p); // push blank onto stack..
849 p = this.getParentElement();
853 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
857 a.push(this.doc.body);
864 getSelection : function()
867 return Roo.isIE ? this.doc.selection : this.win.getSelection();
870 getSelectedNode: function()
872 // this may only work on Gecko!!!
874 // should we cache this!!!!
879 var range = this.createRange(this.getSelection()).cloneRange();
882 var parent = range.parentElement();
884 var testRange = range.duplicate();
885 testRange.moveToElementText(parent);
886 if (testRange.inRange(range)) {
889 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
892 parent = parent.parentElement;
897 // is ancestor a text element.
898 var ac = range.commonAncestorContainer;
899 if (ac.nodeType == 3) {
903 var ar = ac.childNodes;
906 var other_nodes = [];
907 var has_other_nodes = false;
908 for (var i=0;i<ar.length;i++) {
909 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
912 // fullly contained node.
914 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
919 // probably selected..
920 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
921 other_nodes.push(ar[i]);
925 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
930 has_other_nodes = true;
932 if (!nodes.length && other_nodes.length) {
935 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
941 createRange: function(sel)
943 // this has strange effects when using with
944 // top toolbar - not sure if it's a great idea.
945 //this.editor.contentWindow.focus();
946 if (typeof sel != "undefined") {
948 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
950 return this.doc.createRange();
953 return this.doc.createRange();
956 getParentElement: function()
960 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
962 var range = this.createRange(sel);
965 var p = range.commonAncestorContainer;
966 while (p.nodeType == 3) { // text node
977 * Range intersection.. the hard stuff...
981 * [ -- selected range --- ]
985 * if end is before start or hits it. fail.
986 * if start is after end or hits it fail.
988 * if either hits (but other is outside. - then it's not
994 // @see http://www.thismuchiknow.co.uk/?p=64.
995 rangeIntersectsNode : function(range, node)
997 var nodeRange = node.ownerDocument.createRange();
999 nodeRange.selectNode(node);
1001 nodeRange.selectNodeContents(node);
1004 var rangeStartRange = range.cloneRange();
1005 rangeStartRange.collapse(true);
1007 var rangeEndRange = range.cloneRange();
1008 rangeEndRange.collapse(false);
1010 var nodeStartRange = nodeRange.cloneRange();
1011 nodeStartRange.collapse(true);
1013 var nodeEndRange = nodeRange.cloneRange();
1014 nodeEndRange.collapse(false);
1016 return rangeStartRange.compareBoundaryPoints(
1017 Range.START_TO_START, nodeEndRange) == -1 &&
1018 rangeEndRange.compareBoundaryPoints(
1019 Range.START_TO_START, nodeStartRange) == 1;
1023 rangeCompareNode : function(range, node)
1025 var nodeRange = node.ownerDocument.createRange();
1027 nodeRange.selectNode(node);
1029 nodeRange.selectNodeContents(node);
1033 range.collapse(true);
1035 nodeRange.collapse(true);
1037 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1038 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
1040 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1042 var nodeIsBefore = ss == 1;
1043 var nodeIsAfter = ee == -1;
1045 if (nodeIsBefore && nodeIsAfter)
1047 if (!nodeIsBefore && nodeIsAfter)
1048 return 1; //right trailed.
1050 if (nodeIsBefore && !nodeIsAfter)
1051 return 2; // left trailed.
1056 // private? - in a new class?
1057 cleanUpPaste : function()
1059 // cleans up the whole document..
1060 Roo.log('cleanuppaste');
1061 this.cleanUpChildren(this.doc.body);
1062 var clean = this.cleanWordChars(this.doc.body.innerHTML);
1063 if (clean != this.doc.body.innerHTML) {
1064 this.doc.body.innerHTML = clean;
1069 cleanWordChars : function(input) {// change the chars to hex code
1070 var he = Roo.form.HtmlEditor;
1073 Roo.each(he.swapCodes, function(sw) {
1074 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1076 output = output.replace(swapper, sw[1]);
1083 cleanUpChildren : function (n)
1085 if (!n.childNodes.length) {
1088 for (var i = n.childNodes.length-1; i > -1 ; i--) {
1089 this.cleanUpChild(n.childNodes[i]);
1096 cleanUpChild : function (node)
1099 //console.log(node);
1100 if (node.nodeName == "#text") {
1101 // clean up silly Windows -- stuff?
1104 if (node.nodeName == "#comment") {
1105 node.parentNode.removeChild(node);
1106 // clean up silly Windows -- stuff?
1110 if (Roo.form.HtmlEditor.black.indexOf(node.tagName.toLowerCase()) > -1) {
1112 node.parentNode.removeChild(node);
1117 var remove_keep_children= Roo.form.HtmlEditor.remove.indexOf(node.tagName.toLowerCase()) > -1;
1119 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1120 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1122 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1123 // remove_keep_children = true;
1126 if (remove_keep_children) {
1127 this.cleanUpChildren(node);
1128 // inserts everything just before this node...
1129 while (node.childNodes.length) {
1130 var cn = node.childNodes[0];
1131 node.removeChild(cn);
1132 node.parentNode.insertBefore(cn, node);
1134 node.parentNode.removeChild(node);
1138 if (!node.attributes || !node.attributes.length) {
1139 this.cleanUpChildren(node);
1143 function cleanAttr(n,v)
1146 if (v.match(/^\./) || v.match(/^\//)) {
1149 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1152 if (v.match(/^#/)) {
1155 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1156 node.removeAttribute(n);
1160 function cleanStyle(n,v)
1162 if (v.match(/expression/)) { //XSS?? should we even bother..
1163 node.removeAttribute(n);
1166 var cwhite = typeof(ed.cwhite) == 'undefined' ? Roo.form.HtmlEditor.cwhite : ed.cwhite;
1167 var cblack = typeof(ed.cblack) == 'undefined' ? Roo.form.HtmlEditor.cblack : ed.cblack;
1170 var parts = v.split(/;/);
1173 Roo.each(parts, function(p) {
1174 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1178 var l = p.split(':').shift().replace(/\s+/g,'');
1179 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1181 if ( cblack.indexOf(l) > -1) {
1182 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1183 //node.removeAttribute(n);
1187 // only allow 'c whitelisted system attributes'
1188 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1189 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1190 //node.removeAttribute(n);
1201 node.setAttribute(n, clean.join(';'));
1203 node.removeAttribute(n);
1209 for (var i = node.attributes.length-1; i > -1 ; i--) {
1210 var a = node.attributes[i];
1213 if (a.name.toLowerCase().substr(0,2)=='on') {
1214 node.removeAttribute(a.name);
1217 if (Roo.form.HtmlEditor.ablack.indexOf(a.name.toLowerCase()) > -1) {
1218 node.removeAttribute(a.name);
1221 if (Roo.form.HtmlEditor.aclean.indexOf(a.name.toLowerCase()) > -1) {
1222 cleanAttr(a.name,a.value); // fixme..
1225 if (a.name == 'style') {
1226 cleanStyle(a.name,a.value);
1229 /// clean up MS crap..
1230 // tecnically this should be a list of valid class'es..
1233 if (a.name == 'class') {
1234 if (a.value.match(/^Mso/)) {
1235 node.className = '';
1238 if (a.value.match(/body/)) {
1239 node.className = '';
1250 this.cleanUpChildren(node);
1256 // hide stuff that is not compatible
1274 * @cfg {String} fieldClass @hide
1277 * @cfg {String} focusClass @hide
1280 * @cfg {String} autoCreate @hide
1283 * @cfg {String} inputType @hide
1286 * @cfg {String} invalidClass @hide
1289 * @cfg {String} invalidText @hide
1292 * @cfg {String} msgFx @hide
1295 * @cfg {String} validateOnBlur @hide
1299 Roo.form.HtmlEditor.white = [
1300 'area', 'br', 'img', 'input', 'hr', 'wbr',
1302 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1303 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1304 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1305 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1306 'table', 'ul', 'xmp',
1308 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1311 'dir', 'menu', 'ol', 'ul', 'dl',
1317 Roo.form.HtmlEditor.black = [
1318 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1320 'base', 'basefont', 'bgsound', 'blink', 'body',
1321 'frame', 'frameset', 'head', 'html', 'ilayer',
1322 'iframe', 'layer', 'link', 'meta', 'object',
1323 'script', 'style' ,'title', 'xml' // clean later..
1325 Roo.form.HtmlEditor.clean = [
1326 'script', 'style', 'title', 'xml'
1328 Roo.form.HtmlEditor.remove = [
1333 Roo.form.HtmlEditor.ablack = [
1337 Roo.form.HtmlEditor.aclean = [
1338 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1342 Roo.form.HtmlEditor.pwhite= [
1343 'http', 'https', 'mailto'
1346 // white listed style attributes.
1347 Roo.form.HtmlEditor.cwhite= [
1348 // 'text-align', /// default is to allow most things..
1354 // black listed style attributes.
1355 Roo.form.HtmlEditor.cblack= [
1356 // 'font-size' -- this can be set by the project
1360 Roo.form.HtmlEditor.swapCodes =[