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);
27 * Fires when the editor is fully initialized (including the iframe)
28 * @param {Roo.HtmlEditorCore} this
33 * Fires when the editor is first receives the focus. Any insertion must wait
34 * until after this event.
35 * @param {Roo.HtmlEditorCore} this
40 * Fires before the textarea is updated with content from the editor iframe. Return false
42 * @param {Roo.HtmlEditorCore} this
43 * @param {String} html
48 * Fires before the iframe editor is updated with content from the textarea. Return false
50 * @param {Roo.HtmlEditorCore} this
51 * @param {String} html
56 * Fires when the textarea is updated with content from the editor iframe.
57 * @param {Roo.HtmlEditorCore} this
58 * @param {String} html
63 * Fires when the iframe editor is updated with content from the textarea.
64 * @param {Roo.HtmlEditorCore} this
65 * @param {String} html
71 * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
72 * @param {Roo.HtmlEditorCore} this
77 // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
86 Roo.extend(Roo.HtmlEditorCore, Roo.Component, {
90 * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field
96 * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
101 * @cfg {Number} height (in pixels)
105 * @cfg {Number} width (in pixels)
110 * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
118 // private properties
119 validationEvent : false,
123 sourceEditMode : false,
124 onFocus : Roo.emptyFn,
134 * Protected method that will not generally be called directly. It
135 * is called when the editor initializes the iframe with HTML contents. Override this method if you
136 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
138 getDocMarkup : function(){
141 Roo.log(this.stylesheets);
143 // inherit styels from page...??
144 if (this.stylesheets === false) {
146 Roo.get(document.head).select('style').each(function(node) {
147 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
150 Roo.get(document.head).select('link').each(function(node) {
151 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
154 } else if (!this.stylesheets.length) {
156 st = '<style type="text/css">' +
157 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
160 Roo.each(this.stylesheets, function(s) {
161 st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
166 st += '<style type="text/css">' +
167 'IMG { cursor: pointer } ' +
171 return '<html><head>' + st +
172 //<style type="text/css">' +
173 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
175 ' </head><body class="roo-htmleditor-body"></body></html>';
179 onRender : function(ct, position)
182 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
183 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
186 this.el.dom.style.border = '0 none';
187 this.el.dom.setAttribute('tabIndex', -1);
188 this.el.addClass('x-hidden hide');
192 if(Roo.isIE){ // fix IE 1px bogus margin
193 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
197 this.frameId = Roo.id();
201 var iframe = this.owner.wrap.createChild({
203 cls: 'form-control', // bootstrap..
207 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
212 this.iframe = iframe.dom;
216 this.doc.designMode = 'on';
219 this.doc.write(this.getDocMarkup());
223 var task = { // must defer to wait for browser to be ready
225 //console.log("run task?" + this.doc.readyState);
227 if(this.doc.body || this.doc.readyState == 'complete'){
229 this.doc.designMode="on";
233 Roo.TaskMgr.stop(task);
234 this.initEditor.defer(10, this);
241 Roo.TaskMgr.start(task);
248 onResize : function(w, h)
250 Roo.log('resize: ' +w + ',' + h );
251 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
255 if(typeof w == 'number'){
257 this.iframe.style.width = w + 'px';
259 if(typeof h == 'number'){
261 this.iframe.style.height = h + 'px';
263 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
270 * Toggles the editor between standard and source edit mode.
271 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
273 toggleSourceEdit : function(sourceEditMode){
275 this.sourceEditMode = sourceEditMode === true;
277 if(this.sourceEditMode){
279 Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
282 Roo.get(this.iframe).removeClass(['x-hidden','hide']);
283 //this.iframe.className = '';
286 //this.setSize(this.owner.wrap.getSize());
287 //this.fireEvent('editmodechange', this, this.sourceEditMode);
294 * Protected method that will not generally be called directly. If you need/want
295 * custom HTML cleanup, this is the method you should override.
296 * @param {String} html The HTML to be cleaned
297 * return {String} The cleaned HTML
299 cleanHtml : function(html){
302 if(Roo.isSafari){ // strip safari nonsense
303 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
306 if(html == ' '){
313 * HTML Editor -> Textarea
314 * Protected method that will not generally be called directly. Syncs the contents
315 * of the editor iframe with the textarea.
317 syncValue : function(){
318 if(this.initialized){
319 var bd = (this.doc.body || this.doc.documentElement);
320 //this.cleanUpPaste(); -- this is done else where and causes havoc..
321 var html = bd.innerHTML;
323 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
324 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
326 html = '<div style="'+m[0]+'">' + html + '</div>';
329 html = this.cleanHtml(html);
330 // fix up the special chars.. normaly like back quotes in word...
331 // however we do not want to do this with chinese..
332 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
333 var cc = b.charCodeAt();
335 (cc >= 0x4E00 && cc < 0xA000 ) ||
336 (cc >= 0x3400 && cc < 0x4E00 ) ||
337 (cc >= 0xf900 && cc < 0xfb00 )
343 if(this.owner.fireEvent('beforesync', this, html) !== false){
344 this.el.dom.value = html;
345 this.owner.fireEvent('sync', this, html);
351 * Protected method that will not generally be called directly. Pushes the value of the textarea
352 * into the iframe editor.
354 pushValue : function(){
355 if(this.initialized){
356 var v = this.el.dom.value.trim();
362 if(this.owner.fireEvent('beforepush', this, v) !== false){
363 var d = (this.doc.body || this.doc.documentElement);
366 this.el.dom.value = d.innerHTML;
367 this.owner.fireEvent('push', this, v);
373 deferFocus : function(){
374 this.focus.defer(10, this);
379 if(this.win && !this.sourceEditMode){
386 assignDocWin: function()
388 var iframe = this.iframe;
391 this.doc = iframe.contentWindow.document;
392 this.win = iframe.contentWindow;
394 // if (!Roo.get(this.frameId)) {
397 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
398 // this.win = Roo.get(this.frameId).dom.contentWindow;
400 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
404 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
405 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
410 initEditor : function(){
411 //console.log("INIT EDITOR");
416 this.doc.designMode="on";
418 this.doc.write(this.getDocMarkup());
421 var dbody = (this.doc.body || this.doc.documentElement);
422 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
423 // this copies styles from the containing element into thsi one..
424 // not sure why we need all of this..
425 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
427 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
428 //ss['background-attachment'] = 'fixed'; // w3c
429 dbody.bgProperties = 'fixed'; // ie
430 //Roo.DomHelper.applyStyles(dbody, ss);
431 Roo.EventManager.on(this.doc, {
432 //'mousedown': this.onEditorEvent,
433 'mouseup': this.onEditorEvent,
434 'dblclick': this.onEditorEvent,
435 'click': this.onEditorEvent,
436 'keyup': this.onEditorEvent,
441 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
443 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
444 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
446 this.initialized = true;
448 this.owner.fireEvent('initialize', this);
453 onDestroy : function(){
459 //for (var i =0; i < this.toolbars.length;i++) {
460 // // fixme - ask toolbars for heights?
461 // this.toolbars[i].onDestroy();
464 //this.wrap.dom.innerHTML = '';
465 //this.wrap.remove();
470 onFirstFocus : function(){
475 this.activated = true;
478 if(Roo.isGecko){ // prevent silly gecko errors
480 var s = this.win.getSelection();
481 if(!s.focusNode || s.focusNode.nodeType != 3){
482 var r = s.getRangeAt(0);
483 r.selectNodeContents((this.doc.body || this.doc.documentElement));
488 this.execCmd('useCSS', true);
489 this.execCmd('styleWithCSS', false);
492 this.owner.fireEvent('activate', this);
496 adjustFont: function(btn){
497 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
498 //if(Roo.isSafari){ // safari
501 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
502 if(Roo.isSafari){ // safari
503 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
504 v = (v < 10) ? 10 : v;
505 v = (v > 48) ? 48 : v;
506 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
511 v = Math.max(1, v+adjust);
513 this.execCmd('FontSize', v );
516 onEditorEvent : function(e){
517 this.owner.fireEvent('editorevent', this, e);
518 // this.updateToolbar();
519 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
522 insertTag : function(tg)
524 // could be a bit smarter... -> wrap the current selected tRoo..
525 if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
527 range = this.createRange(this.getSelection());
528 var wrappingNode = this.doc.createElement(tg.toLowerCase());
529 wrappingNode.appendChild(range.extractContents());
530 range.insertNode(wrappingNode);
537 this.execCmd("formatblock", tg);
541 insertText : function(txt)
545 var range = this.createRange();
546 range.deleteContents();
547 //alert(Sender.getAttribute('label'));
549 range.insertNode(this.doc.createTextNode(txt));
555 * Executes a Midas editor command on the editor document and performs necessary focus and
556 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
557 * @param {String} cmd The Midas command
558 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
560 relayCmd : function(cmd, value){
562 this.execCmd(cmd, value);
563 this.owner.fireEvent('editorevent', this);
564 //this.updateToolbar();
565 this.owner.deferFocus();
569 * Executes a Midas editor command directly on the editor document.
570 * For visual commands, you should use {@link #relayCmd} instead.
571 * <b>This should only be called after the editor is initialized.</b>
572 * @param {String} cmd The Midas command
573 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
575 execCmd : function(cmd, value){
576 this.doc.execCommand(cmd, false, value === undefined ? null : value);
583 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
585 * @param {String} text | dom node..
587 insertAtCursor : function(text)
598 var r = this.doc.selection.createRange();
609 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
613 // from jquery ui (MIT licenced)
617 if (win.getSelection && win.getSelection().getRangeAt) {
618 range = win.getSelection().getRangeAt(0);
619 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
620 range.insertNode(node);
621 } else if (win.document.selection && win.document.selection.createRange) {
622 // no firefox support
623 var txt = typeof(text) == 'string' ? text : text.outerHTML;
624 win.document.selection.createRange().pasteHTML(txt);
626 // no firefox support
627 var txt = typeof(text) == 'string' ? text : text.outerHTML;
628 this.execCmd('InsertHTML', txt);
637 mozKeyPress : function(e){
639 var c = e.getCharCode(), cmd;
642 c = String.fromCharCode(c).toLowerCase();
656 this.cleanUpPaste.defer(100, this);
672 fixKeys : function(){ // load time branching for fastest keydown performance
675 var k = e.getKey(), r;
678 r = this.doc.selection.createRange();
681 r.pasteHTML('    ');
688 r = this.doc.selection.createRange();
690 var target = r.parentElement();
691 if(!target || target.tagName.toLowerCase() != 'li'){
693 r.pasteHTML('<br />');
699 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
700 this.cleanUpPaste.defer(100, this);
706 }else if(Roo.isOpera){
712 this.execCmd('InsertHTML','    ');
715 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
716 this.cleanUpPaste.defer(100, this);
721 }else if(Roo.isSafari){
727 this.execCmd('InsertText','\t');
731 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
732 this.cleanUpPaste.defer(100, this);
740 getAllAncestors: function()
742 var p = this.getSelectedNode();
745 a.push(p); // push blank onto stack..
746 p = this.getParentElement();
750 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
754 a.push(this.doc.body);
761 getSelection : function()
764 return Roo.isIE ? this.doc.selection : this.win.getSelection();
767 getSelectedNode: function()
769 // this may only work on Gecko!!!
771 // should we cache this!!!!
776 var range = this.createRange(this.getSelection()).cloneRange();
779 var parent = range.parentElement();
781 var testRange = range.duplicate();
782 testRange.moveToElementText(parent);
783 if (testRange.inRange(range)) {
786 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
789 parent = parent.parentElement;
794 // is ancestor a text element.
795 var ac = range.commonAncestorContainer;
796 if (ac.nodeType == 3) {
800 var ar = ac.childNodes;
803 var other_nodes = [];
804 var has_other_nodes = false;
805 for (var i=0;i<ar.length;i++) {
806 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
809 // fullly contained node.
811 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
816 // probably selected..
817 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
818 other_nodes.push(ar[i]);
822 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
827 has_other_nodes = true;
829 if (!nodes.length && other_nodes.length) {
832 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
838 createRange: function(sel)
840 // this has strange effects when using with
841 // top toolbar - not sure if it's a great idea.
842 //this.editor.contentWindow.focus();
843 if (typeof sel != "undefined") {
845 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
847 return this.doc.createRange();
850 return this.doc.createRange();
853 getParentElement: function()
857 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
859 var range = this.createRange(sel);
862 var p = range.commonAncestorContainer;
863 while (p.nodeType == 3) { // text node
874 * Range intersection.. the hard stuff...
878 * [ -- selected range --- ]
882 * if end is before start or hits it. fail.
883 * if start is after end or hits it fail.
885 * if either hits (but other is outside. - then it's not
891 // @see http://www.thismuchiknow.co.uk/?p=64.
892 rangeIntersectsNode : function(range, node)
894 var nodeRange = node.ownerDocument.createRange();
896 nodeRange.selectNode(node);
898 nodeRange.selectNodeContents(node);
901 var rangeStartRange = range.cloneRange();
902 rangeStartRange.collapse(true);
904 var rangeEndRange = range.cloneRange();
905 rangeEndRange.collapse(false);
907 var nodeStartRange = nodeRange.cloneRange();
908 nodeStartRange.collapse(true);
910 var nodeEndRange = nodeRange.cloneRange();
911 nodeEndRange.collapse(false);
913 return rangeStartRange.compareBoundaryPoints(
914 Range.START_TO_START, nodeEndRange) == -1 &&
915 rangeEndRange.compareBoundaryPoints(
916 Range.START_TO_START, nodeStartRange) == 1;
920 rangeCompareNode : function(range, node)
922 var nodeRange = node.ownerDocument.createRange();
924 nodeRange.selectNode(node);
926 nodeRange.selectNodeContents(node);
930 range.collapse(true);
932 nodeRange.collapse(true);
934 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
935 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
937 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
939 var nodeIsBefore = ss == 1;
940 var nodeIsAfter = ee == -1;
942 if (nodeIsBefore && nodeIsAfter)
944 if (!nodeIsBefore && nodeIsAfter)
945 return 1; //right trailed.
947 if (nodeIsBefore && !nodeIsAfter)
948 return 2; // left trailed.
953 // private? - in a new class?
954 cleanUpPaste : function()
956 // cleans up the whole document..
957 Roo.log('cleanuppaste');
959 this.cleanUpChildren(this.doc.body);
960 var clean = this.cleanWordChars(this.doc.body.innerHTML);
961 if (clean != this.doc.body.innerHTML) {
962 this.doc.body.innerHTML = clean;
967 cleanWordChars : function(input) {// change the chars to hex code
968 var he = Roo.HtmlEditorCore;
971 Roo.each(he.swapCodes, function(sw) {
972 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
974 output = output.replace(swapper, sw[1]);
981 cleanUpChildren : function (n)
983 if (!n.childNodes.length) {
986 for (var i = n.childNodes.length-1; i > -1 ; i--) {
987 this.cleanUpChild(n.childNodes[i]);
994 cleanUpChild : function (node)
998 if (node.nodeName == "#text") {
999 // clean up silly Windows -- stuff?
1002 if (node.nodeName == "#comment") {
1003 node.parentNode.removeChild(node);
1004 // clean up silly Windows -- stuff?
1008 if (Roo.HtmlEditorCore.black.indexOf(node.tagName.toLowerCase()) > -1 && this.clearUp) {
1010 node.parentNode.removeChild(node);
1015 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1017 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1018 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1020 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1021 // remove_keep_children = true;
1024 if (remove_keep_children) {
1025 this.cleanUpChildren(node);
1026 // inserts everything just before this node...
1027 while (node.childNodes.length) {
1028 var cn = node.childNodes[0];
1029 node.removeChild(cn);
1030 node.parentNode.insertBefore(cn, node);
1032 node.parentNode.removeChild(node);
1036 if (!node.attributes || !node.attributes.length) {
1037 this.cleanUpChildren(node);
1041 function cleanAttr(n,v)
1044 if (v.match(/^\./) || v.match(/^\//)) {
1047 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1050 if (v.match(/^#/)) {
1053 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1054 node.removeAttribute(n);
1058 function cleanStyle(n,v)
1060 if (v.match(/expression/)) { //XSS?? should we even bother..
1061 node.removeAttribute(n);
1064 var cwhite = typeof(ed.cwhite) != 'undefined' && ed.cwhite !== false ? ed.cwhite : Roo.HtmlEditorCore.cwhite;
1065 var cblack = typeof(ed.cblack) != 'undefined' && ed.cwhite !== false ? ed.cblack : Roo.HtmlEditorCore.cblack;
1068 var parts = v.split(/;/);
1071 Roo.each(parts, function(p) {
1072 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1076 var l = p.split(':').shift().replace(/\s+/g,'');
1077 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1079 if ( cblack.indexOf(l) > -1) {
1080 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1081 //node.removeAttribute(n);
1085 // only allow 'c whitelisted system attributes'
1086 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1087 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1088 //node.removeAttribute(n);
1099 node.setAttribute(n, clean.join(';'));
1101 node.removeAttribute(n);
1107 for (var i = node.attributes.length-1; i > -1 ; i--) {
1108 var a = node.attributes[i];
1111 if (a.name.toLowerCase().substr(0,2)=='on') {
1112 node.removeAttribute(a.name);
1115 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1116 node.removeAttribute(a.name);
1119 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1120 cleanAttr(a.name,a.value); // fixme..
1123 if (a.name == 'style') {
1124 cleanStyle(a.name,a.value);
1127 /// clean up MS crap..
1128 // tecnically this should be a list of valid class'es..
1131 if (a.name == 'class') {
1132 if (a.value.match(/^Mso/)) {
1133 node.className = '';
1136 if (a.value.match(/body/)) {
1137 node.className = '';
1148 this.cleanUpChildren(node);
1153 * Clean up MS wordisms...
1155 cleanWord : function(node)
1158 var cleanWordChildren = function()
1160 if (!node.childNodes.length) {
1163 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1164 _t.cleanWord(node.childNodes[i]);
1170 this.cleanWord(this.doc.body);
1173 if (node.nodeName == "#text") {
1174 // clean up silly Windows -- stuff?
1177 if (node.nodeName == "#comment") {
1178 node.parentNode.removeChild(node);
1179 // clean up silly Windows -- stuff?
1183 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1184 node.parentNode.removeChild(node);
1188 // remove - but keep children..
1189 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1190 while (node.childNodes.length) {
1191 var cn = node.childNodes[0];
1192 node.removeChild(cn);
1193 node.parentNode.insertBefore(cn, node);
1195 node.parentNode.removeChild(node);
1196 cleanWordChildren();
1200 if (node.className.length) {
1202 var cn = node.className.split(/\W+/);
1204 Roo.each(cn, function(cls) {
1205 if (cls.match(/Mso[a-zA-Z]+/)) {
1210 node.className = cna.length ? cna.join(' ') : '';
1212 node.removeAttribute("class");
1216 if (node.hasAttribute("lang")) {
1217 node.removeAttribute("lang");
1220 if (node.hasAttribute("style")) {
1222 var styles = node.getAttribute("style").split(";");
1224 Roo.each(styles, function(s) {
1225 if (!s.match(/:/)) {
1228 var kv = s.split(":");
1229 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1232 // what ever is left... we allow.
1235 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1236 if (!nstyle.length) {
1237 node.removeAttribute('style');
1241 cleanWordChildren();
1245 domToHTML : function(currentElement, depth, nopadtext) {
1248 nopadtext = nopadtext || false;
1250 if (!currentElement) {
1251 return this.domToHTML(this.doc.body);
1254 //Roo.log(currentElement);
1256 var allText = false;
1257 var nodeName = currentElement.nodeName;
1258 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1260 if (nodeName == '#text') {
1261 return currentElement.nodeValue;
1266 if (nodeName != 'BODY') {
1269 // Prints the node tagName, such as <A>, <IMG>, etc
1272 for(i = 0; i < currentElement.attributes.length;i++) {
1274 var aname = currentElement.attributes.item(i).name;
1275 if (!currentElement.attributes.item(i).value.length) {
1278 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1281 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1290 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1293 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1298 // Traverse the tree
1300 var currentElementChild = currentElement.childNodes.item(i);
1304 while (currentElementChild) {
1305 // Formatting code (indent the tree so it looks nice on the screen)
1306 var nopad = nopadtext;
1307 if (lastnode == 'SPAN') {
1311 if (currentElementChild.nodeName == '#text') {
1312 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1313 if (!nopad && toadd.length > 80) {
1314 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1319 currentElementChild = currentElement.childNodes.item(i);
1325 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1327 // Recursively traverse the tree structure of the child node
1328 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1329 lastnode = currentElementChild.nodeName;
1331 currentElementChild=currentElement.childNodes.item(i);
1337 // The remaining code is mostly for formatting the tree
1338 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1343 ret+= "</"+tagName+">";
1349 // hide stuff that is not compatible
1367 * @cfg {String} fieldClass @hide
1370 * @cfg {String} focusClass @hide
1373 * @cfg {String} autoCreate @hide
1376 * @cfg {String} inputType @hide
1379 * @cfg {String} invalidClass @hide
1382 * @cfg {String} invalidText @hide
1385 * @cfg {String} msgFx @hide
1388 * @cfg {String} validateOnBlur @hide
1392 Roo.HtmlEditorCore.white = [
1393 'area', 'br', 'img', 'input', 'hr', 'wbr',
1395 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1396 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1397 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1398 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1399 'table', 'ul', 'xmp',
1401 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1404 'dir', 'menu', 'ol', 'ul', 'dl',
1410 Roo.HtmlEditorCore.black = [
1411 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1413 'base', 'basefont', 'bgsound', 'blink', 'body',
1414 'frame', 'frameset', 'head', 'html', 'ilayer',
1415 'iframe', 'layer', 'link', 'meta', 'object',
1416 'script', 'style' ,'title', 'xml' // clean later..
1418 Roo.HtmlEditorCore.clean = [
1419 'script', 'style', 'title', 'xml'
1421 Roo.HtmlEditorCore.remove = [
1426 Roo.HtmlEditorCore.ablack = [
1430 Roo.HtmlEditorCore.aclean = [
1431 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1435 Roo.HtmlEditorCore.pwhite= [
1436 'http', 'https', 'mailto'
1439 // white listed style attributes.
1440 Roo.HtmlEditorCore.cwhite= [
1441 // 'text-align', /// default is to allow most things..
1447 // black listed style attributes.
1448 Roo.HtmlEditorCore.cblack= [
1449 // 'font-size' -- this can be set by the project
1453 Roo.HtmlEditorCore.swapCodes =[