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
79 // defaults : white / black...
81 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
82 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
86 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
87 if (b.indexOf(tag) > -1) {
94 Roo.each(w, function(tag) {
95 if (b.indexOf(tag) > -1) {
98 if (this.white.indexOf(tag) > -1) {
101 this.white.push(tag);
106 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
107 if (w.indexOf(tag) > -1) {
110 this.black.push(tag);
114 Roo.each(w, function(tag) {
115 if (b.indexOf(tag) > -1) {
118 if (this.white.indexOf(tag) > -1) {
121 this.white.push(tag);
130 Roo.extend(Roo.HtmlEditorCore, Roo.Component, {
134 * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field
140 * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
145 * @cfg {Number} height (in pixels)
149 * @cfg {Number} width (in pixels)
154 * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
162 // private properties
163 validationEvent : false,
167 sourceEditMode : false,
168 onFocus : Roo.emptyFn,
174 // blacklist + whitelisted elements..
181 * Protected method that will not generally be called directly. It
182 * is called when the editor initializes the iframe with HTML contents. Override this method if you
183 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
185 getDocMarkup : function(){
188 Roo.log(this.stylesheets);
190 // inherit styels from page...??
191 if (this.stylesheets === false) {
193 Roo.get(document.head).select('style').each(function(node) {
194 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
197 Roo.get(document.head).select('link').each(function(node) {
198 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
201 } else if (!this.stylesheets.length) {
203 st = '<style type="text/css">' +
204 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
207 Roo.each(this.stylesheets, function(s) {
208 st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
213 st += '<style type="text/css">' +
214 'IMG { cursor: pointer } ' +
218 return '<html><head>' + st +
219 //<style type="text/css">' +
220 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
222 ' </head><body class="roo-htmleditor-body"></body></html>';
226 onRender : function(ct, position)
229 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
230 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
233 this.el.dom.style.border = '0 none';
234 this.el.dom.setAttribute('tabIndex', -1);
235 this.el.addClass('x-hidden hide');
239 if(Roo.isIE){ // fix IE 1px bogus margin
240 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
244 this.frameId = Roo.id();
248 var iframe = this.owner.wrap.createChild({
250 cls: 'form-control', // bootstrap..
254 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
259 this.iframe = iframe.dom;
263 this.doc.designMode = 'on';
266 this.doc.write(this.getDocMarkup());
270 var task = { // must defer to wait for browser to be ready
272 //console.log("run task?" + this.doc.readyState);
274 if(this.doc.body || this.doc.readyState == 'complete'){
276 this.doc.designMode="on";
280 Roo.TaskMgr.stop(task);
281 this.initEditor.defer(10, this);
288 Roo.TaskMgr.start(task);
295 onResize : function(w, h)
297 Roo.log('resize: ' +w + ',' + h );
298 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
302 if(typeof w == 'number'){
304 this.iframe.style.width = w + 'px';
306 if(typeof h == 'number'){
308 this.iframe.style.height = h + 'px';
310 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
317 * Toggles the editor between standard and source edit mode.
318 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
320 toggleSourceEdit : function(sourceEditMode){
322 this.sourceEditMode = sourceEditMode === true;
324 if(this.sourceEditMode){
326 Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
329 Roo.get(this.iframe).removeClass(['x-hidden','hide']);
330 //this.iframe.className = '';
333 //this.setSize(this.owner.wrap.getSize());
334 //this.fireEvent('editmodechange', this, this.sourceEditMode);
341 * Protected method that will not generally be called directly. If you need/want
342 * custom HTML cleanup, this is the method you should override.
343 * @param {String} html The HTML to be cleaned
344 * return {String} The cleaned HTML
346 cleanHtml : function(html){
349 if(Roo.isSafari){ // strip safari nonsense
350 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
353 if(html == ' '){
360 * HTML Editor -> Textarea
361 * Protected method that will not generally be called directly. Syncs the contents
362 * of the editor iframe with the textarea.
364 syncValue : function(){
365 if(this.initialized){
366 var bd = (this.doc.body || this.doc.documentElement);
367 //this.cleanUpPaste(); -- this is done else where and causes havoc..
368 var html = bd.innerHTML;
370 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
371 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
373 html = '<div style="'+m[0]+'">' + html + '</div>';
376 html = this.cleanHtml(html);
377 // fix up the special chars.. normaly like back quotes in word...
378 // however we do not want to do this with chinese..
379 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
380 var cc = b.charCodeAt();
382 (cc >= 0x4E00 && cc < 0xA000 ) ||
383 (cc >= 0x3400 && cc < 0x4E00 ) ||
384 (cc >= 0xf900 && cc < 0xfb00 )
390 if(this.owner.fireEvent('beforesync', this, html) !== false){
391 this.el.dom.value = html;
392 this.owner.fireEvent('sync', this, html);
398 * Protected method that will not generally be called directly. Pushes the value of the textarea
399 * into the iframe editor.
401 pushValue : function(){
402 if(this.initialized){
403 var v = this.el.dom.value.trim();
409 if(this.owner.fireEvent('beforepush', this, v) !== false){
410 var d = (this.doc.body || this.doc.documentElement);
413 this.el.dom.value = d.innerHTML;
414 this.owner.fireEvent('push', this, v);
420 deferFocus : function(){
421 this.focus.defer(10, this);
426 if(this.win && !this.sourceEditMode){
433 assignDocWin: function()
435 var iframe = this.iframe;
438 this.doc = iframe.contentWindow.document;
439 this.win = iframe.contentWindow;
441 // if (!Roo.get(this.frameId)) {
444 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
445 // this.win = Roo.get(this.frameId).dom.contentWindow;
447 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
451 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
452 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
457 initEditor : function(){
458 //console.log("INIT EDITOR");
463 this.doc.designMode="on";
465 this.doc.write(this.getDocMarkup());
468 var dbody = (this.doc.body || this.doc.documentElement);
469 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
470 // this copies styles from the containing element into thsi one..
471 // not sure why we need all of this..
472 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
474 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
475 //ss['background-attachment'] = 'fixed'; // w3c
476 dbody.bgProperties = 'fixed'; // ie
477 //Roo.DomHelper.applyStyles(dbody, ss);
478 Roo.EventManager.on(this.doc, {
479 //'mousedown': this.onEditorEvent,
480 'mouseup': this.onEditorEvent,
481 'dblclick': this.onEditorEvent,
482 'click': this.onEditorEvent,
483 'keyup': this.onEditorEvent,
488 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
490 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
491 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
493 this.initialized = true;
495 this.owner.fireEvent('initialize', this);
500 onDestroy : function(){
506 //for (var i =0; i < this.toolbars.length;i++) {
507 // // fixme - ask toolbars for heights?
508 // this.toolbars[i].onDestroy();
511 //this.wrap.dom.innerHTML = '';
512 //this.wrap.remove();
517 onFirstFocus : function(){
522 this.activated = true;
525 if(Roo.isGecko){ // prevent silly gecko errors
527 var s = this.win.getSelection();
528 if(!s.focusNode || s.focusNode.nodeType != 3){
529 var r = s.getRangeAt(0);
530 r.selectNodeContents((this.doc.body || this.doc.documentElement));
535 this.execCmd('useCSS', true);
536 this.execCmd('styleWithCSS', false);
539 this.owner.fireEvent('activate', this);
543 adjustFont: function(btn){
544 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
545 //if(Roo.isSafari){ // safari
548 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
549 if(Roo.isSafari){ // safari
550 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
551 v = (v < 10) ? 10 : v;
552 v = (v > 48) ? 48 : v;
553 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
558 v = Math.max(1, v+adjust);
560 this.execCmd('FontSize', v );
563 onEditorEvent : function(e){
564 this.owner.fireEvent('editorevent', this, e);
565 // this.updateToolbar();
566 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
569 insertTag : function(tg)
571 // could be a bit smarter... -> wrap the current selected tRoo..
572 if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
574 range = this.createRange(this.getSelection());
575 var wrappingNode = this.doc.createElement(tg.toLowerCase());
576 wrappingNode.appendChild(range.extractContents());
577 range.insertNode(wrappingNode);
584 this.execCmd("formatblock", tg);
588 insertText : function(txt)
592 var range = this.createRange();
593 range.deleteContents();
594 //alert(Sender.getAttribute('label'));
596 range.insertNode(this.doc.createTextNode(txt));
602 * Executes a Midas editor command on the editor document and performs necessary focus and
603 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
604 * @param {String} cmd The Midas command
605 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
607 relayCmd : function(cmd, value){
609 this.execCmd(cmd, value);
610 this.owner.fireEvent('editorevent', this);
611 //this.updateToolbar();
612 this.owner.deferFocus();
616 * Executes a Midas editor command directly on the editor document.
617 * For visual commands, you should use {@link #relayCmd} instead.
618 * <b>This should only be called after the editor is initialized.</b>
619 * @param {String} cmd The Midas command
620 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
622 execCmd : function(cmd, value){
623 this.doc.execCommand(cmd, false, value === undefined ? null : value);
630 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
632 * @param {String} text | dom node..
634 insertAtCursor : function(text)
645 var r = this.doc.selection.createRange();
656 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
660 // from jquery ui (MIT licenced)
664 if (win.getSelection && win.getSelection().getRangeAt) {
665 range = win.getSelection().getRangeAt(0);
666 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
667 range.insertNode(node);
668 } else if (win.document.selection && win.document.selection.createRange) {
669 // no firefox support
670 var txt = typeof(text) == 'string' ? text : text.outerHTML;
671 win.document.selection.createRange().pasteHTML(txt);
673 // no firefox support
674 var txt = typeof(text) == 'string' ? text : text.outerHTML;
675 this.execCmd('InsertHTML', txt);
684 mozKeyPress : function(e){
686 var c = e.getCharCode(), cmd;
689 c = String.fromCharCode(c).toLowerCase();
703 this.cleanUpPaste.defer(100, this);
719 fixKeys : function(){ // load time branching for fastest keydown performance
722 var k = e.getKey(), r;
725 r = this.doc.selection.createRange();
728 r.pasteHTML('    ');
735 r = this.doc.selection.createRange();
737 var target = r.parentElement();
738 if(!target || target.tagName.toLowerCase() != 'li'){
740 r.pasteHTML('<br />');
746 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
747 this.cleanUpPaste.defer(100, this);
753 }else if(Roo.isOpera){
759 this.execCmd('InsertHTML','    ');
762 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
763 this.cleanUpPaste.defer(100, this);
768 }else if(Roo.isSafari){
774 this.execCmd('InsertText','\t');
778 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
779 this.cleanUpPaste.defer(100, this);
787 getAllAncestors: function()
789 var p = this.getSelectedNode();
792 a.push(p); // push blank onto stack..
793 p = this.getParentElement();
797 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
801 a.push(this.doc.body);
808 getSelection : function()
811 return Roo.isIE ? this.doc.selection : this.win.getSelection();
814 getSelectedNode: function()
816 // this may only work on Gecko!!!
818 // should we cache this!!!!
823 var range = this.createRange(this.getSelection()).cloneRange();
826 var parent = range.parentElement();
828 var testRange = range.duplicate();
829 testRange.moveToElementText(parent);
830 if (testRange.inRange(range)) {
833 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
836 parent = parent.parentElement;
841 // is ancestor a text element.
842 var ac = range.commonAncestorContainer;
843 if (ac.nodeType == 3) {
847 var ar = ac.childNodes;
850 var other_nodes = [];
851 var has_other_nodes = false;
852 for (var i=0;i<ar.length;i++) {
853 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
856 // fullly contained node.
858 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
863 // probably selected..
864 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
865 other_nodes.push(ar[i]);
869 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
874 has_other_nodes = true;
876 if (!nodes.length && other_nodes.length) {
879 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
885 createRange: function(sel)
887 // this has strange effects when using with
888 // top toolbar - not sure if it's a great idea.
889 //this.editor.contentWindow.focus();
890 if (typeof sel != "undefined") {
892 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
894 return this.doc.createRange();
897 return this.doc.createRange();
900 getParentElement: function()
904 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
906 var range = this.createRange(sel);
909 var p = range.commonAncestorContainer;
910 while (p.nodeType == 3) { // text node
921 * Range intersection.. the hard stuff...
925 * [ -- selected range --- ]
929 * if end is before start or hits it. fail.
930 * if start is after end or hits it fail.
932 * if either hits (but other is outside. - then it's not
938 // @see http://www.thismuchiknow.co.uk/?p=64.
939 rangeIntersectsNode : function(range, node)
941 var nodeRange = node.ownerDocument.createRange();
943 nodeRange.selectNode(node);
945 nodeRange.selectNodeContents(node);
948 var rangeStartRange = range.cloneRange();
949 rangeStartRange.collapse(true);
951 var rangeEndRange = range.cloneRange();
952 rangeEndRange.collapse(false);
954 var nodeStartRange = nodeRange.cloneRange();
955 nodeStartRange.collapse(true);
957 var nodeEndRange = nodeRange.cloneRange();
958 nodeEndRange.collapse(false);
960 return rangeStartRange.compareBoundaryPoints(
961 Range.START_TO_START, nodeEndRange) == -1 &&
962 rangeEndRange.compareBoundaryPoints(
963 Range.START_TO_START, nodeStartRange) == 1;
967 rangeCompareNode : function(range, node)
969 var nodeRange = node.ownerDocument.createRange();
971 nodeRange.selectNode(node);
973 nodeRange.selectNodeContents(node);
977 range.collapse(true);
979 nodeRange.collapse(true);
981 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
982 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
984 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
986 var nodeIsBefore = ss == 1;
987 var nodeIsAfter = ee == -1;
989 if (nodeIsBefore && nodeIsAfter)
991 if (!nodeIsBefore && nodeIsAfter)
992 return 1; //right trailed.
994 if (nodeIsBefore && !nodeIsAfter)
995 return 2; // left trailed.
1000 // private? - in a new class?
1001 cleanUpPaste : function()
1003 // cleans up the whole document..
1004 Roo.log('cleanuppaste');
1006 this.cleanUpChildren(this.doc.body);
1007 var clean = this.cleanWordChars(this.doc.body.innerHTML);
1008 if (clean != this.doc.body.innerHTML) {
1009 this.doc.body.innerHTML = clean;
1014 cleanWordChars : function(input) {// change the chars to hex code
1015 var he = Roo.HtmlEditorCore;
1018 Roo.each(he.swapCodes, function(sw) {
1019 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1021 output = output.replace(swapper, sw[1]);
1028 cleanUpChildren : function (n)
1030 if (!n.childNodes.length) {
1033 for (var i = n.childNodes.length-1; i > -1 ; i--) {
1034 this.cleanUpChild(n.childNodes[i]);
1041 cleanUpChild : function (node)
1044 //console.log(node);
1045 if (node.nodeName == "#text") {
1046 // clean up silly Windows -- stuff?
1049 if (node.nodeName == "#comment") {
1050 node.parentNode.removeChild(node);
1051 // clean up silly Windows -- stuff?
1055 if (Roo.HtmlEditorCore.black.indexOf(node.tagName.toLowerCase()) > -1 && this.clearUp) {
1057 node.parentNode.removeChild(node);
1062 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1064 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1065 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1067 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1068 // remove_keep_children = true;
1071 if (remove_keep_children) {
1072 this.cleanUpChildren(node);
1073 // inserts everything just before this node...
1074 while (node.childNodes.length) {
1075 var cn = node.childNodes[0];
1076 node.removeChild(cn);
1077 node.parentNode.insertBefore(cn, node);
1079 node.parentNode.removeChild(node);
1083 if (!node.attributes || !node.attributes.length) {
1084 this.cleanUpChildren(node);
1088 function cleanAttr(n,v)
1091 if (v.match(/^\./) || v.match(/^\//)) {
1094 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1097 if (v.match(/^#/)) {
1100 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1101 node.removeAttribute(n);
1105 function cleanStyle(n,v)
1107 if (v.match(/expression/)) { //XSS?? should we even bother..
1108 node.removeAttribute(n);
1111 var cwhite = typeof(ed.cwhite) != 'undefined' && ed.cwhite !== false ? ed.cwhite : Roo.HtmlEditorCore.cwhite;
1112 var cblack = typeof(ed.cblack) != 'undefined' && ed.cwhite !== false ? ed.cblack : Roo.HtmlEditorCore.cblack;
1115 var parts = v.split(/;/);
1118 Roo.each(parts, function(p) {
1119 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1123 var l = p.split(':').shift().replace(/\s+/g,'');
1124 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1126 if ( cblack.indexOf(l) > -1) {
1127 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1128 //node.removeAttribute(n);
1132 // only allow 'c whitelisted system attributes'
1133 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1134 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1135 //node.removeAttribute(n);
1146 node.setAttribute(n, clean.join(';'));
1148 node.removeAttribute(n);
1154 for (var i = node.attributes.length-1; i > -1 ; i--) {
1155 var a = node.attributes[i];
1158 if (a.name.toLowerCase().substr(0,2)=='on') {
1159 node.removeAttribute(a.name);
1162 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1163 node.removeAttribute(a.name);
1166 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1167 cleanAttr(a.name,a.value); // fixme..
1170 if (a.name == 'style') {
1171 cleanStyle(a.name,a.value);
1174 /// clean up MS crap..
1175 // tecnically this should be a list of valid class'es..
1178 if (a.name == 'class') {
1179 if (a.value.match(/^Mso/)) {
1180 node.className = '';
1183 if (a.value.match(/body/)) {
1184 node.className = '';
1195 this.cleanUpChildren(node);
1200 * Clean up MS wordisms...
1202 cleanWord : function(node)
1205 var cleanWordChildren = function()
1207 if (!node.childNodes.length) {
1210 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1211 _t.cleanWord(node.childNodes[i]);
1217 this.cleanWord(this.doc.body);
1220 if (node.nodeName == "#text") {
1221 // clean up silly Windows -- stuff?
1224 if (node.nodeName == "#comment") {
1225 node.parentNode.removeChild(node);
1226 // clean up silly Windows -- stuff?
1230 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1231 node.parentNode.removeChild(node);
1235 // remove - but keep children..
1236 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1237 while (node.childNodes.length) {
1238 var cn = node.childNodes[0];
1239 node.removeChild(cn);
1240 node.parentNode.insertBefore(cn, node);
1242 node.parentNode.removeChild(node);
1243 cleanWordChildren();
1247 if (node.className.length) {
1249 var cn = node.className.split(/\W+/);
1251 Roo.each(cn, function(cls) {
1252 if (cls.match(/Mso[a-zA-Z]+/)) {
1257 node.className = cna.length ? cna.join(' ') : '';
1259 node.removeAttribute("class");
1263 if (node.hasAttribute("lang")) {
1264 node.removeAttribute("lang");
1267 if (node.hasAttribute("style")) {
1269 var styles = node.getAttribute("style").split(";");
1271 Roo.each(styles, function(s) {
1272 if (!s.match(/:/)) {
1275 var kv = s.split(":");
1276 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1279 // what ever is left... we allow.
1282 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1283 if (!nstyle.length) {
1284 node.removeAttribute('style');
1288 cleanWordChildren();
1292 domToHTML : function(currentElement, depth, nopadtext) {
1295 nopadtext = nopadtext || false;
1297 if (!currentElement) {
1298 return this.domToHTML(this.doc.body);
1301 //Roo.log(currentElement);
1303 var allText = false;
1304 var nodeName = currentElement.nodeName;
1305 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1307 if (nodeName == '#text') {
1308 return currentElement.nodeValue;
1313 if (nodeName != 'BODY') {
1316 // Prints the node tagName, such as <A>, <IMG>, etc
1319 for(i = 0; i < currentElement.attributes.length;i++) {
1321 var aname = currentElement.attributes.item(i).name;
1322 if (!currentElement.attributes.item(i).value.length) {
1325 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1328 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1337 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1340 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1345 // Traverse the tree
1347 var currentElementChild = currentElement.childNodes.item(i);
1351 while (currentElementChild) {
1352 // Formatting code (indent the tree so it looks nice on the screen)
1353 var nopad = nopadtext;
1354 if (lastnode == 'SPAN') {
1358 if (currentElementChild.nodeName == '#text') {
1359 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1360 if (!nopad && toadd.length > 80) {
1361 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1366 currentElementChild = currentElement.childNodes.item(i);
1372 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1374 // Recursively traverse the tree structure of the child node
1375 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1376 lastnode = currentElementChild.nodeName;
1378 currentElementChild=currentElement.childNodes.item(i);
1384 // The remaining code is mostly for formatting the tree
1385 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1390 ret+= "</"+tagName+">";
1396 // hide stuff that is not compatible
1414 * @cfg {String} fieldClass @hide
1417 * @cfg {String} focusClass @hide
1420 * @cfg {String} autoCreate @hide
1423 * @cfg {String} inputType @hide
1426 * @cfg {String} invalidClass @hide
1429 * @cfg {String} invalidText @hide
1432 * @cfg {String} msgFx @hide
1435 * @cfg {String} validateOnBlur @hide
1439 Roo.HtmlEditorCore.white = [
1440 'area', 'br', 'img', 'input', 'hr', 'wbr',
1442 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1443 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1444 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1445 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1446 'table', 'ul', 'xmp',
1448 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1451 'dir', 'menu', 'ol', 'ul', 'dl',
1457 Roo.HtmlEditorCore.black = [
1458 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1460 'base', 'basefont', 'bgsound', 'blink', 'body',
1461 'frame', 'frameset', 'head', 'html', 'ilayer',
1462 'iframe', 'layer', 'link', 'meta', 'object',
1463 'script', 'style' ,'title', 'xml' // clean later..
1465 Roo.HtmlEditorCore.clean = [
1466 'script', 'style', 'title', 'xml'
1468 Roo.HtmlEditorCore.remove = [
1473 Roo.HtmlEditorCore.ablack = [
1477 Roo.HtmlEditorCore.aclean = [
1478 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1482 Roo.HtmlEditorCore.pwhite= [
1483 'http', 'https', 'mailto'
1486 // white listed style attributes.
1487 Roo.HtmlEditorCore.cwhite= [
1488 // 'text-align', /// default is to allow most things..
1494 // black listed style attributes.
1495 Roo.HtmlEditorCore.cblack= [
1496 // 'font-size' -- this can be set by the project
1500 Roo.HtmlEditorCore.swapCodes =[