1 //<script type="text/javascript">
4 * Based Ext JS Library 1.1.1
5 * Copyright(c) 2006-2007, Ext JS, LLC.
11 * @class Roo.HtmlEditorCore
12 * @extends Roo.Component
13 * Provides a the editing component for the HTML editors in Roo. (bootstrap and Roo.form)
15 * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
18 Roo.HtmlEditorCore = function(config){
21 Roo.HtmlEditorCore.superclass.constructor.call(this, config);
25 * Fires when the editor is fully initialized (including the iframe)
26 * @param {Roo.HtmlEditorCore} this
31 * Fires when the editor is first receives the focus. Any insertion must wait
32 * until after this event.
33 * @param {Roo.HtmlEditorCore} this
38 * Fires before the textarea is updated with content from the editor iframe. Return false
40 * @param {Roo.HtmlEditorCore} this
41 * @param {String} html
46 * Fires before the iframe editor is updated with content from the textarea. Return false
48 * @param {Roo.HtmlEditorCore} this
49 * @param {String} html
54 * Fires when the textarea is updated with content from the editor iframe.
55 * @param {Roo.HtmlEditorCore} this
56 * @param {String} html
61 * Fires when the iframe editor is updated with content from the textarea.
62 * @param {Roo.HtmlEditorCore} this
63 * @param {String} html
69 * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
70 * @param {Roo.HtmlEditorCore} this
78 Roo.extend(Roo.HtmlEditorCore, Roo.Component, {
82 * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field
88 * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
93 * @cfg {Number} height (in pixels)
97 * @cfg {Number} width (in pixels)
102 * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
110 // private properties
111 validationEvent : false,
115 sourceEditMode : false,
116 onFocus : Roo.emptyFn,
126 * Protected method that will not generally be called directly. It
127 * is called when the editor initializes the iframe with HTML contents. Override this method if you
128 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
130 getDocMarkup : function(){
133 Roo.log(this.stylesheets);
135 // inherit styels from page...??
136 if (this.stylesheets === false) {
138 Roo.get(document.head).select('style').each(function(node) {
139 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
142 Roo.get(document.head).select('link').each(function(node) {
143 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
146 } else if (!this.stylesheets.length) {
148 st = '<style type="text/css">' +
149 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
152 Roo.each(this.stylesheets, function(s) {
153 st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
158 st += '<style type="text/css">' +
159 'IMG { cursor: pointer } ' +
163 return '<html><head>' + st +
164 //<style type="text/css">' +
165 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
167 ' </head><body class="roo-htmleditor-body"></body></html>';
171 onRender : function(ct, position)
174 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
175 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
178 this.el.dom.style.border = '0 none';
179 this.el.dom.setAttribute('tabIndex', -1);
180 this.el.addClass('x-hidden hide');
184 if(Roo.isIE){ // fix IE 1px bogus margin
185 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
189 this.frameId = Roo.id();
193 var iframe = this.owner.wrap.createChild({
195 cls: 'form-control', // bootstrap..
199 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
204 this.iframe = iframe.dom;
208 this.doc.designMode = 'on';
211 this.doc.write(this.getDocMarkup());
215 var task = { // must defer to wait for browser to be ready
217 //console.log("run task?" + this.doc.readyState);
219 if(this.doc.body || this.doc.readyState == 'complete'){
221 this.doc.designMode="on";
225 Roo.TaskMgr.stop(task);
226 this.initEditor.defer(10, this);
233 Roo.TaskMgr.start(task);
240 onResize : function(w, h)
242 Roo.log('resize: ' +w + ',' + h );
243 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
247 if(typeof w == 'number'){
249 this.iframe.style.width = w + 'px';
251 if(typeof h == 'number'){
253 this.iframe.style.height = h + 'px';
255 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
262 * Toggles the editor between standard and source edit mode.
263 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
265 toggleSourceEdit : function(sourceEditMode){
267 this.sourceEditMode = sourceEditMode === true;
269 if(this.sourceEditMode){
271 Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
274 Roo.get(this.iframe).removeClass(['x-hidden','hide']);
275 //this.iframe.className = '';
278 //this.setSize(this.owner.wrap.getSize());
279 //this.fireEvent('editmodechange', this, this.sourceEditMode);
286 * Protected method that will not generally be called directly. If you need/want
287 * custom HTML cleanup, this is the method you should override.
288 * @param {String} html The HTML to be cleaned
289 * return {String} The cleaned HTML
291 cleanHtml : function(html){
294 if(Roo.isSafari){ // strip safari nonsense
295 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
298 if(html == ' '){
305 * HTML Editor -> Textarea
306 * Protected method that will not generally be called directly. Syncs the contents
307 * of the editor iframe with the textarea.
309 syncValue : function(){
310 if(this.initialized){
311 var bd = (this.doc.body || this.doc.documentElement);
312 //this.cleanUpPaste(); -- this is done else where and causes havoc..
313 var html = bd.innerHTML;
315 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
316 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
318 html = '<div style="'+m[0]+'">' + html + '</div>';
321 html = this.cleanHtml(html);
322 // fix up the special chars.. normaly like back quotes in word...
323 // however we do not want to do this with chinese..
324 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
325 var cc = b.charCodeAt();
327 (cc >= 0x4E00 && cc < 0xA000 ) ||
328 (cc >= 0x3400 && cc < 0x4E00 ) ||
329 (cc >= 0xf900 && cc < 0xfb00 )
335 if(this.owner.fireEvent('beforesync', this, html) !== false){
336 this.el.dom.value = html;
337 this.owner.fireEvent('sync', this, html);
343 * Protected method that will not generally be called directly. Pushes the value of the textarea
344 * into the iframe editor.
346 pushValue : function(){
347 if(this.initialized){
348 var v = this.el.dom.value.trim();
354 if(this.owner.fireEvent('beforepush', this, v) !== false){
355 var d = (this.doc.body || this.doc.documentElement);
358 this.el.dom.value = d.innerHTML;
359 this.owner.fireEvent('push', this, v);
365 deferFocus : function(){
366 this.focus.defer(10, this);
371 if(this.win && !this.sourceEditMode){
378 assignDocWin: function()
380 var iframe = this.iframe;
383 this.doc = iframe.contentWindow.document;
384 this.win = iframe.contentWindow;
386 if (!Roo.get(this.frameId)) {
389 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
390 this.win = Roo.get(this.frameId).dom.contentWindow;
395 initEditor : function(){
396 //console.log("INIT EDITOR");
401 this.doc.designMode="on";
403 this.doc.write(this.getDocMarkup());
406 var dbody = (this.doc.body || this.doc.documentElement);
407 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
408 // this copies styles from the containing element into thsi one..
409 // not sure why we need all of this..
410 var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
411 ss['background-attachment'] = 'fixed'; // w3c
412 dbody.bgProperties = 'fixed'; // ie
413 Roo.DomHelper.applyStyles(dbody, ss);
414 Roo.EventManager.on(this.doc, {
415 //'mousedown': this.onEditorEvent,
416 'mouseup': this.onEditorEvent,
417 'dblclick': this.onEditorEvent,
418 'click': this.onEditorEvent,
419 'keyup': this.onEditorEvent,
424 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
426 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
427 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
429 this.initialized = true;
431 this.owner.fireEvent('initialize', this);
436 onDestroy : function(){
442 //for (var i =0; i < this.toolbars.length;i++) {
443 // // fixme - ask toolbars for heights?
444 // this.toolbars[i].onDestroy();
447 //this.wrap.dom.innerHTML = '';
448 //this.wrap.remove();
453 onFirstFocus : function(){
458 this.activated = true;
461 if(Roo.isGecko){ // prevent silly gecko errors
463 var s = this.win.getSelection();
464 if(!s.focusNode || s.focusNode.nodeType != 3){
465 var r = s.getRangeAt(0);
466 r.selectNodeContents((this.doc.body || this.doc.documentElement));
471 this.execCmd('useCSS', true);
472 this.execCmd('styleWithCSS', false);
475 this.owner.fireEvent('activate', this);
479 adjustFont: function(btn){
480 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
481 //if(Roo.isSafari){ // safari
484 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
485 if(Roo.isSafari){ // safari
486 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
487 v = (v < 10) ? 10 : v;
488 v = (v > 48) ? 48 : v;
489 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
494 v = Math.max(1, v+adjust);
496 this.execCmd('FontSize', v );
499 onEditorEvent : function(e){
500 this.owner.fireEvent('editorevent', this, e);
501 // this.updateToolbar();
502 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
505 insertTag : function(tg)
507 // could be a bit smarter... -> wrap the current selected tRoo..
508 if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
510 range = this.createRange(this.getSelection());
511 var wrappingNode = this.doc.createElement(tg.toLowerCase());
512 wrappingNode.appendChild(range.extractContents());
513 range.insertNode(wrappingNode);
520 this.execCmd("formatblock", tg);
524 insertText : function(txt)
528 var range = this.createRange();
529 range.deleteContents();
530 //alert(Sender.getAttribute('label'));
532 range.insertNode(this.doc.createTextNode(txt));
538 * Executes a Midas editor command on the editor document and performs necessary focus and
539 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
540 * @param {String} cmd The Midas command
541 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
543 relayCmd : function(cmd, value){
545 this.execCmd(cmd, value);
546 this.owner.fireEvent('editorevent', this);
547 //this.updateToolbar();
548 this.owner.deferFocus();
552 * Executes a Midas editor command directly on the editor document.
553 * For visual commands, you should use {@link #relayCmd} instead.
554 * <b>This should only be called after the editor is initialized.</b>
555 * @param {String} cmd The Midas command
556 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
558 execCmd : function(cmd, value){
559 this.doc.execCommand(cmd, false, value === undefined ? null : value);
566 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
568 * @param {String} text | dom node..
570 insertAtCursor : function(text)
581 var r = this.doc.selection.createRange();
592 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
596 // from jquery ui (MIT licenced)
600 if (win.getSelection && win.getSelection().getRangeAt) {
601 range = win.getSelection().getRangeAt(0);
602 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
603 range.insertNode(node);
604 } else if (win.document.selection && win.document.selection.createRange) {
605 // no firefox support
606 var txt = typeof(text) == 'string' ? text : text.outerHTML;
607 win.document.selection.createRange().pasteHTML(txt);
609 // no firefox support
610 var txt = typeof(text) == 'string' ? text : text.outerHTML;
611 this.execCmd('InsertHTML', txt);
620 mozKeyPress : function(e){
622 var c = e.getCharCode(), cmd;
625 c = String.fromCharCode(c).toLowerCase();
639 this.cleanUpPaste.defer(100, this);
655 fixKeys : function(){ // load time branching for fastest keydown performance
658 var k = e.getKey(), r;
661 r = this.doc.selection.createRange();
664 r.pasteHTML('    ');
671 r = this.doc.selection.createRange();
673 var target = r.parentElement();
674 if(!target || target.tagName.toLowerCase() != 'li'){
676 r.pasteHTML('<br />');
682 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
683 this.cleanUpPaste.defer(100, this);
689 }else if(Roo.isOpera){
695 this.execCmd('InsertHTML','    ');
698 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
699 this.cleanUpPaste.defer(100, this);
704 }else if(Roo.isSafari){
710 this.execCmd('InsertText','\t');
714 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
715 this.cleanUpPaste.defer(100, this);
723 getAllAncestors: function()
725 var p = this.getSelectedNode();
728 a.push(p); // push blank onto stack..
729 p = this.getParentElement();
733 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
737 a.push(this.doc.body);
744 getSelection : function()
747 return Roo.isIE ? this.doc.selection : this.win.getSelection();
750 getSelectedNode: function()
752 // this may only work on Gecko!!!
754 // should we cache this!!!!
759 var range = this.createRange(this.getSelection()).cloneRange();
762 var parent = range.parentElement();
764 var testRange = range.duplicate();
765 testRange.moveToElementText(parent);
766 if (testRange.inRange(range)) {
769 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
772 parent = parent.parentElement;
777 // is ancestor a text element.
778 var ac = range.commonAncestorContainer;
779 if (ac.nodeType == 3) {
783 var ar = ac.childNodes;
786 var other_nodes = [];
787 var has_other_nodes = false;
788 for (var i=0;i<ar.length;i++) {
789 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
792 // fullly contained node.
794 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
799 // probably selected..
800 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
801 other_nodes.push(ar[i]);
805 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
810 has_other_nodes = true;
812 if (!nodes.length && other_nodes.length) {
815 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
821 createRange: function(sel)
823 // this has strange effects when using with
824 // top toolbar - not sure if it's a great idea.
825 //this.editor.contentWindow.focus();
826 if (typeof sel != "undefined") {
828 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
830 return this.doc.createRange();
833 return this.doc.createRange();
836 getParentElement: function()
840 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
842 var range = this.createRange(sel);
845 var p = range.commonAncestorContainer;
846 while (p.nodeType == 3) { // text node
857 * Range intersection.. the hard stuff...
861 * [ -- selected range --- ]
865 * if end is before start or hits it. fail.
866 * if start is after end or hits it fail.
868 * if either hits (but other is outside. - then it's not
874 // @see http://www.thismuchiknow.co.uk/?p=64.
875 rangeIntersectsNode : function(range, node)
877 var nodeRange = node.ownerDocument.createRange();
879 nodeRange.selectNode(node);
881 nodeRange.selectNodeContents(node);
884 var rangeStartRange = range.cloneRange();
885 rangeStartRange.collapse(true);
887 var rangeEndRange = range.cloneRange();
888 rangeEndRange.collapse(false);
890 var nodeStartRange = nodeRange.cloneRange();
891 nodeStartRange.collapse(true);
893 var nodeEndRange = nodeRange.cloneRange();
894 nodeEndRange.collapse(false);
896 return rangeStartRange.compareBoundaryPoints(
897 Range.START_TO_START, nodeEndRange) == -1 &&
898 rangeEndRange.compareBoundaryPoints(
899 Range.START_TO_START, nodeStartRange) == 1;
903 rangeCompareNode : function(range, node)
905 var nodeRange = node.ownerDocument.createRange();
907 nodeRange.selectNode(node);
909 nodeRange.selectNodeContents(node);
913 range.collapse(true);
915 nodeRange.collapse(true);
917 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
918 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
920 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
922 var nodeIsBefore = ss == 1;
923 var nodeIsAfter = ee == -1;
925 if (nodeIsBefore && nodeIsAfter)
927 if (!nodeIsBefore && nodeIsAfter)
928 return 1; //right trailed.
930 if (nodeIsBefore && !nodeIsAfter)
931 return 2; // left trailed.
936 // private? - in a new class?
937 cleanUpPaste : function()
939 // cleans up the whole document..
940 Roo.log('cleanuppaste');
942 this.cleanUpChildren(this.doc.body);
943 var clean = this.cleanWordChars(this.doc.body.innerHTML);
944 if (clean != this.doc.body.innerHTML) {
945 this.doc.body.innerHTML = clean;
950 cleanWordChars : function(input) {// change the chars to hex code
951 var he = Roo.HtmlEditorCore;
954 Roo.each(he.swapCodes, function(sw) {
955 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
957 output = output.replace(swapper, sw[1]);
964 cleanUpChildren : function (n)
966 if (!n.childNodes.length) {
969 for (var i = n.childNodes.length-1; i > -1 ; i--) {
970 this.cleanUpChild(n.childNodes[i]);
977 cleanUpChild : function (node)
981 if (node.nodeName == "#text") {
982 // clean up silly Windows -- stuff?
985 if (node.nodeName == "#comment") {
986 node.parentNode.removeChild(node);
987 // clean up silly Windows -- stuff?
991 if (Roo.HtmlEditorCore.black.indexOf(node.tagName.toLowerCase()) > -1 && this.clearUp) {
993 node.parentNode.removeChild(node);
998 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1000 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1001 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1003 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1004 // remove_keep_children = true;
1007 if (remove_keep_children) {
1008 this.cleanUpChildren(node);
1009 // inserts everything just before this node...
1010 while (node.childNodes.length) {
1011 var cn = node.childNodes[0];
1012 node.removeChild(cn);
1013 node.parentNode.insertBefore(cn, node);
1015 node.parentNode.removeChild(node);
1019 if (!node.attributes || !node.attributes.length) {
1020 this.cleanUpChildren(node);
1024 function cleanAttr(n,v)
1027 if (v.match(/^\./) || v.match(/^\//)) {
1030 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1033 if (v.match(/^#/)) {
1036 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1037 node.removeAttribute(n);
1041 function cleanStyle(n,v)
1043 if (v.match(/expression/)) { //XSS?? should we even bother..
1044 node.removeAttribute(n);
1047 var cwhite = typeof(ed.cwhite) == 'undefined' ? Roo.HtmlEditorCore.cwhite : ed.cwhite;
1048 var cblack = typeof(ed.cblack) == 'undefined' ? Roo.HtmlEditorCore.cblack : ed.cblack;
1051 var parts = v.split(/;/);
1054 Roo.each(parts, function(p) {
1055 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1059 var l = p.split(':').shift().replace(/\s+/g,'');
1060 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1062 if ( cblack.indexOf(l) > -1) {
1063 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1064 //node.removeAttribute(n);
1068 // only allow 'c whitelisted system attributes'
1069 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1070 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1071 //node.removeAttribute(n);
1082 node.setAttribute(n, clean.join(';'));
1084 node.removeAttribute(n);
1090 for (var i = node.attributes.length-1; i > -1 ; i--) {
1091 var a = node.attributes[i];
1094 if (a.name.toLowerCase().substr(0,2)=='on') {
1095 node.removeAttribute(a.name);
1098 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1099 node.removeAttribute(a.name);
1102 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1103 cleanAttr(a.name,a.value); // fixme..
1106 if (a.name == 'style') {
1107 cleanStyle(a.name,a.value);
1110 /// clean up MS crap..
1111 // tecnically this should be a list of valid class'es..
1114 if (a.name == 'class') {
1115 if (a.value.match(/^Mso/)) {
1116 node.className = '';
1119 if (a.value.match(/body/)) {
1120 node.className = '';
1131 this.cleanUpChildren(node);
1136 * Clean up MS wordisms...
1138 cleanWord : function(node)
1140 var cleanWordChildren = function()
1142 if (!node.childNodes.length) {
1145 for (var i = n.childNodes.length-1; i > -1 ; i--) {
1146 this.cleanWord(n.childNodes[i]);
1152 this.cleanWord(this.doc.body);
1155 if (node.nodeName == "#text") {
1156 // clean up silly Windows -- stuff?
1159 if (node.nodeName == "#comment") {
1160 node.parentNode.removeChild(node);
1161 // clean up silly Windows -- stuff?
1165 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1166 node.parentNode.removeChild(node);
1170 // remove - but keep children..
1171 if (node.tagName.toLowerCase().match(/^(meta|link|span|\\?xml:|st1:|o:|font)/)) {
1172 while (node.childNodes.length) {
1173 var cn = node.childNodes[0];
1174 node.removeChild(cn);
1175 node.parentNode.insertBefore(cn, node);
1177 node.parentNode.removeChild(node);
1178 cleanWordChildren();
1182 if (node.className.length) {
1184 var cn = node.className.split(/\W+/);
1186 Roo.each(cn, function(cls) {
1187 if (cls.match(/Mso[a-zA-Z]+/)) {
1192 node.className = cna.length ? cna.join(' ') : '';
1194 node.removeAttribute("class");
1197 if (node.hasAttribute("lang")) {
1198 node.removeAttribute("lang");
1200 if (node.getAttribute("style").length) {
1202 var styles = node.getAttribute("style").split(";");
1204 Roo.each(styles, function(s) {
1205 if (!s.match(/:/)) {
1208 var kv = s.split(":");
1209 if (kv[0].match(/^(mso-|line|font|background|margin|padding|/)) {
1212 // what ever is left... we allow.
1215 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1216 if (!nstyle.length) {
1217 node.removeAttribute('style');
1228 // hide stuff that is not compatible
1246 * @cfg {String} fieldClass @hide
1249 * @cfg {String} focusClass @hide
1252 * @cfg {String} autoCreate @hide
1255 * @cfg {String} inputType @hide
1258 * @cfg {String} invalidClass @hide
1261 * @cfg {String} invalidText @hide
1264 * @cfg {String} msgFx @hide
1267 * @cfg {String} validateOnBlur @hide
1271 Roo.HtmlEditorCore.white = [
1272 'area', 'br', 'img', 'input', 'hr', 'wbr',
1274 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1275 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1276 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1277 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1278 'table', 'ul', 'xmp',
1280 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1283 'dir', 'menu', 'ol', 'ul', 'dl',
1289 Roo.HtmlEditorCore.black = [
1290 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1292 'base', 'basefont', 'bgsound', 'blink', 'body',
1293 'frame', 'frameset', 'head', 'html', 'ilayer',
1294 'iframe', 'layer', 'link', 'meta', 'object',
1295 'script', 'style' ,'title', 'xml' // clean later..
1297 Roo.HtmlEditorCore.clean = [
1298 'script', 'style', 'title', 'xml'
1300 Roo.HtmlEditorCore.remove = [
1305 Roo.HtmlEditorCore.ablack = [
1309 Roo.HtmlEditorCore.aclean = [
1310 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1314 Roo.HtmlEditorCore.pwhite= [
1315 'http', 'https', 'mailto'
1318 // white listed style attributes.
1319 Roo.HtmlEditorCore.cwhite= [
1320 // 'text-align', /// default is to allow most things..
1326 // black listed style attributes.
1327 Roo.HtmlEditorCore.cblack= [
1328 // 'font-size' -- this can be set by the project
1332 Roo.HtmlEditorCore.swapCodes =[