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 : [];
85 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
86 if (b.indexOf(tag) > -1) {
93 if (typeof(this.owner.white) != 'undefined' && this.owner.white) {
94 Roo.each(this.owner.white, function(tag) {
95 if (this.white.indexOf(tag) > -1) {
107 Roo.extend(Roo.HtmlEditorCore, Roo.Component, {
111 * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field
117 * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
122 * @cfg {Number} height (in pixels)
126 * @cfg {Number} width (in pixels)
131 * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
139 // private properties
140 validationEvent : false,
144 sourceEditMode : false,
145 onFocus : Roo.emptyFn,
151 // blacklist + whitelisted elements..
158 * Protected method that will not generally be called directly. It
159 * is called when the editor initializes the iframe with HTML contents. Override this method if you
160 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
162 getDocMarkup : function(){
165 Roo.log(this.stylesheets);
167 // inherit styels from page...??
168 if (this.stylesheets === false) {
170 Roo.get(document.head).select('style').each(function(node) {
171 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
174 Roo.get(document.head).select('link').each(function(node) {
175 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
178 } else if (!this.stylesheets.length) {
180 st = '<style type="text/css">' +
181 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
184 Roo.each(this.stylesheets, function(s) {
185 st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
190 st += '<style type="text/css">' +
191 'IMG { cursor: pointer } ' +
195 return '<html><head>' + st +
196 //<style type="text/css">' +
197 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
199 ' </head><body class="roo-htmleditor-body"></body></html>';
203 onRender : function(ct, position)
206 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
207 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
210 this.el.dom.style.border = '0 none';
211 this.el.dom.setAttribute('tabIndex', -1);
212 this.el.addClass('x-hidden hide');
216 if(Roo.isIE){ // fix IE 1px bogus margin
217 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
221 this.frameId = Roo.id();
225 var iframe = this.owner.wrap.createChild({
227 cls: 'form-control', // bootstrap..
231 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
236 this.iframe = iframe.dom;
240 this.doc.designMode = 'on';
243 this.doc.write(this.getDocMarkup());
247 var task = { // must defer to wait for browser to be ready
249 //console.log("run task?" + this.doc.readyState);
251 if(this.doc.body || this.doc.readyState == 'complete'){
253 this.doc.designMode="on";
257 Roo.TaskMgr.stop(task);
258 this.initEditor.defer(10, this);
265 Roo.TaskMgr.start(task);
272 onResize : function(w, h)
274 Roo.log('resize: ' +w + ',' + h );
275 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
279 if(typeof w == 'number'){
281 this.iframe.style.width = w + 'px';
283 if(typeof h == 'number'){
285 this.iframe.style.height = h + 'px';
287 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
294 * Toggles the editor between standard and source edit mode.
295 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
297 toggleSourceEdit : function(sourceEditMode){
299 this.sourceEditMode = sourceEditMode === true;
301 if(this.sourceEditMode){
303 Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
306 Roo.get(this.iframe).removeClass(['x-hidden','hide']);
307 //this.iframe.className = '';
310 //this.setSize(this.owner.wrap.getSize());
311 //this.fireEvent('editmodechange', this, this.sourceEditMode);
318 * Protected method that will not generally be called directly. If you need/want
319 * custom HTML cleanup, this is the method you should override.
320 * @param {String} html The HTML to be cleaned
321 * return {String} The cleaned HTML
323 cleanHtml : function(html){
326 if(Roo.isSafari){ // strip safari nonsense
327 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
330 if(html == ' '){
337 * HTML Editor -> Textarea
338 * Protected method that will not generally be called directly. Syncs the contents
339 * of the editor iframe with the textarea.
341 syncValue : function(){
342 if(this.initialized){
343 var bd = (this.doc.body || this.doc.documentElement);
344 //this.cleanUpPaste(); -- this is done else where and causes havoc..
345 var html = bd.innerHTML;
347 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
348 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
350 html = '<div style="'+m[0]+'">' + html + '</div>';
353 html = this.cleanHtml(html);
354 // fix up the special chars.. normaly like back quotes in word...
355 // however we do not want to do this with chinese..
356 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
357 var cc = b.charCodeAt();
359 (cc >= 0x4E00 && cc < 0xA000 ) ||
360 (cc >= 0x3400 && cc < 0x4E00 ) ||
361 (cc >= 0xf900 && cc < 0xfb00 )
367 if(this.owner.fireEvent('beforesync', this, html) !== false){
368 this.el.dom.value = html;
369 this.owner.fireEvent('sync', this, html);
375 * Protected method that will not generally be called directly. Pushes the value of the textarea
376 * into the iframe editor.
378 pushValue : function(){
379 if(this.initialized){
380 var v = this.el.dom.value.trim();
386 if(this.owner.fireEvent('beforepush', this, v) !== false){
387 var d = (this.doc.body || this.doc.documentElement);
390 this.el.dom.value = d.innerHTML;
391 this.owner.fireEvent('push', this, v);
397 deferFocus : function(){
398 this.focus.defer(10, this);
403 if(this.win && !this.sourceEditMode){
410 assignDocWin: function()
412 var iframe = this.iframe;
415 this.doc = iframe.contentWindow.document;
416 this.win = iframe.contentWindow;
418 // if (!Roo.get(this.frameId)) {
421 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
422 // this.win = Roo.get(this.frameId).dom.contentWindow;
424 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
428 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
429 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
434 initEditor : function(){
435 //console.log("INIT EDITOR");
440 this.doc.designMode="on";
442 this.doc.write(this.getDocMarkup());
445 var dbody = (this.doc.body || this.doc.documentElement);
446 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
447 // this copies styles from the containing element into thsi one..
448 // not sure why we need all of this..
449 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
451 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
452 //ss['background-attachment'] = 'fixed'; // w3c
453 dbody.bgProperties = 'fixed'; // ie
454 //Roo.DomHelper.applyStyles(dbody, ss);
455 Roo.EventManager.on(this.doc, {
456 //'mousedown': this.onEditorEvent,
457 'mouseup': this.onEditorEvent,
458 'dblclick': this.onEditorEvent,
459 'click': this.onEditorEvent,
460 'keyup': this.onEditorEvent,
465 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
467 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
468 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
470 this.initialized = true;
472 this.owner.fireEvent('initialize', this);
477 onDestroy : function(){
483 //for (var i =0; i < this.toolbars.length;i++) {
484 // // fixme - ask toolbars for heights?
485 // this.toolbars[i].onDestroy();
488 //this.wrap.dom.innerHTML = '';
489 //this.wrap.remove();
494 onFirstFocus : function(){
499 this.activated = true;
502 if(Roo.isGecko){ // prevent silly gecko errors
504 var s = this.win.getSelection();
505 if(!s.focusNode || s.focusNode.nodeType != 3){
506 var r = s.getRangeAt(0);
507 r.selectNodeContents((this.doc.body || this.doc.documentElement));
512 this.execCmd('useCSS', true);
513 this.execCmd('styleWithCSS', false);
516 this.owner.fireEvent('activate', this);
520 adjustFont: function(btn){
521 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
522 //if(Roo.isSafari){ // safari
525 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
526 if(Roo.isSafari){ // safari
527 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
528 v = (v < 10) ? 10 : v;
529 v = (v > 48) ? 48 : v;
530 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
535 v = Math.max(1, v+adjust);
537 this.execCmd('FontSize', v );
540 onEditorEvent : function(e){
541 this.owner.fireEvent('editorevent', this, e);
542 // this.updateToolbar();
543 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
546 insertTag : function(tg)
548 // could be a bit smarter... -> wrap the current selected tRoo..
549 if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
551 range = this.createRange(this.getSelection());
552 var wrappingNode = this.doc.createElement(tg.toLowerCase());
553 wrappingNode.appendChild(range.extractContents());
554 range.insertNode(wrappingNode);
561 this.execCmd("formatblock", tg);
565 insertText : function(txt)
569 var range = this.createRange();
570 range.deleteContents();
571 //alert(Sender.getAttribute('label'));
573 range.insertNode(this.doc.createTextNode(txt));
579 * Executes a Midas editor command on the editor document and performs necessary focus and
580 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
581 * @param {String} cmd The Midas command
582 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
584 relayCmd : function(cmd, value){
586 this.execCmd(cmd, value);
587 this.owner.fireEvent('editorevent', this);
588 //this.updateToolbar();
589 this.owner.deferFocus();
593 * Executes a Midas editor command directly on the editor document.
594 * For visual commands, you should use {@link #relayCmd} instead.
595 * <b>This should only be called after the editor is initialized.</b>
596 * @param {String} cmd The Midas command
597 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
599 execCmd : function(cmd, value){
600 this.doc.execCommand(cmd, false, value === undefined ? null : value);
607 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
609 * @param {String} text | dom node..
611 insertAtCursor : function(text)
622 var r = this.doc.selection.createRange();
633 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
637 // from jquery ui (MIT licenced)
641 if (win.getSelection && win.getSelection().getRangeAt) {
642 range = win.getSelection().getRangeAt(0);
643 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
644 range.insertNode(node);
645 } else if (win.document.selection && win.document.selection.createRange) {
646 // no firefox support
647 var txt = typeof(text) == 'string' ? text : text.outerHTML;
648 win.document.selection.createRange().pasteHTML(txt);
650 // no firefox support
651 var txt = typeof(text) == 'string' ? text : text.outerHTML;
652 this.execCmd('InsertHTML', txt);
661 mozKeyPress : function(e){
663 var c = e.getCharCode(), cmd;
666 c = String.fromCharCode(c).toLowerCase();
680 this.cleanUpPaste.defer(100, this);
696 fixKeys : function(){ // load time branching for fastest keydown performance
699 var k = e.getKey(), r;
702 r = this.doc.selection.createRange();
705 r.pasteHTML('    ');
712 r = this.doc.selection.createRange();
714 var target = r.parentElement();
715 if(!target || target.tagName.toLowerCase() != 'li'){
717 r.pasteHTML('<br />');
723 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
724 this.cleanUpPaste.defer(100, this);
730 }else if(Roo.isOpera){
736 this.execCmd('InsertHTML','    ');
739 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
740 this.cleanUpPaste.defer(100, this);
745 }else if(Roo.isSafari){
751 this.execCmd('InsertText','\t');
755 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
756 this.cleanUpPaste.defer(100, this);
764 getAllAncestors: function()
766 var p = this.getSelectedNode();
769 a.push(p); // push blank onto stack..
770 p = this.getParentElement();
774 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
778 a.push(this.doc.body);
785 getSelection : function()
788 return Roo.isIE ? this.doc.selection : this.win.getSelection();
791 getSelectedNode: function()
793 // this may only work on Gecko!!!
795 // should we cache this!!!!
800 var range = this.createRange(this.getSelection()).cloneRange();
803 var parent = range.parentElement();
805 var testRange = range.duplicate();
806 testRange.moveToElementText(parent);
807 if (testRange.inRange(range)) {
810 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
813 parent = parent.parentElement;
818 // is ancestor a text element.
819 var ac = range.commonAncestorContainer;
820 if (ac.nodeType == 3) {
824 var ar = ac.childNodes;
827 var other_nodes = [];
828 var has_other_nodes = false;
829 for (var i=0;i<ar.length;i++) {
830 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
833 // fullly contained node.
835 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
840 // probably selected..
841 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
842 other_nodes.push(ar[i]);
846 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
851 has_other_nodes = true;
853 if (!nodes.length && other_nodes.length) {
856 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
862 createRange: function(sel)
864 // this has strange effects when using with
865 // top toolbar - not sure if it's a great idea.
866 //this.editor.contentWindow.focus();
867 if (typeof sel != "undefined") {
869 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
871 return this.doc.createRange();
874 return this.doc.createRange();
877 getParentElement: function()
881 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
883 var range = this.createRange(sel);
886 var p = range.commonAncestorContainer;
887 while (p.nodeType == 3) { // text node
898 * Range intersection.. the hard stuff...
902 * [ -- selected range --- ]
906 * if end is before start or hits it. fail.
907 * if start is after end or hits it fail.
909 * if either hits (but other is outside. - then it's not
915 // @see http://www.thismuchiknow.co.uk/?p=64.
916 rangeIntersectsNode : function(range, node)
918 var nodeRange = node.ownerDocument.createRange();
920 nodeRange.selectNode(node);
922 nodeRange.selectNodeContents(node);
925 var rangeStartRange = range.cloneRange();
926 rangeStartRange.collapse(true);
928 var rangeEndRange = range.cloneRange();
929 rangeEndRange.collapse(false);
931 var nodeStartRange = nodeRange.cloneRange();
932 nodeStartRange.collapse(true);
934 var nodeEndRange = nodeRange.cloneRange();
935 nodeEndRange.collapse(false);
937 return rangeStartRange.compareBoundaryPoints(
938 Range.START_TO_START, nodeEndRange) == -1 &&
939 rangeEndRange.compareBoundaryPoints(
940 Range.START_TO_START, nodeStartRange) == 1;
944 rangeCompareNode : function(range, node)
946 var nodeRange = node.ownerDocument.createRange();
948 nodeRange.selectNode(node);
950 nodeRange.selectNodeContents(node);
954 range.collapse(true);
956 nodeRange.collapse(true);
958 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
959 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
961 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
963 var nodeIsBefore = ss == 1;
964 var nodeIsAfter = ee == -1;
966 if (nodeIsBefore && nodeIsAfter)
968 if (!nodeIsBefore && nodeIsAfter)
969 return 1; //right trailed.
971 if (nodeIsBefore && !nodeIsAfter)
972 return 2; // left trailed.
977 // private? - in a new class?
978 cleanUpPaste : function()
980 // cleans up the whole document..
981 Roo.log('cleanuppaste');
983 this.cleanUpChildren(this.doc.body);
984 var clean = this.cleanWordChars(this.doc.body.innerHTML);
985 if (clean != this.doc.body.innerHTML) {
986 this.doc.body.innerHTML = clean;
991 cleanWordChars : function(input) {// change the chars to hex code
992 var he = Roo.HtmlEditorCore;
995 Roo.each(he.swapCodes, function(sw) {
996 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
998 output = output.replace(swapper, sw[1]);
1005 cleanUpChildren : function (n)
1007 if (!n.childNodes.length) {
1010 for (var i = n.childNodes.length-1; i > -1 ; i--) {
1011 this.cleanUpChild(n.childNodes[i]);
1018 cleanUpChild : function (node)
1021 //console.log(node);
1022 if (node.nodeName == "#text") {
1023 // clean up silly Windows -- stuff?
1026 if (node.nodeName == "#comment") {
1027 node.parentNode.removeChild(node);
1028 // clean up silly Windows -- stuff?
1032 if (Roo.HtmlEditorCore.black.indexOf(node.tagName.toLowerCase()) > -1 && this.clearUp) {
1034 node.parentNode.removeChild(node);
1039 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1041 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1042 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1044 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1045 // remove_keep_children = true;
1048 if (remove_keep_children) {
1049 this.cleanUpChildren(node);
1050 // inserts everything just before this node...
1051 while (node.childNodes.length) {
1052 var cn = node.childNodes[0];
1053 node.removeChild(cn);
1054 node.parentNode.insertBefore(cn, node);
1056 node.parentNode.removeChild(node);
1060 if (!node.attributes || !node.attributes.length) {
1061 this.cleanUpChildren(node);
1065 function cleanAttr(n,v)
1068 if (v.match(/^\./) || v.match(/^\//)) {
1071 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1074 if (v.match(/^#/)) {
1077 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1078 node.removeAttribute(n);
1082 function cleanStyle(n,v)
1084 if (v.match(/expression/)) { //XSS?? should we even bother..
1085 node.removeAttribute(n);
1088 var cwhite = typeof(ed.cwhite) != 'undefined' && ed.cwhite !== false ? ed.cwhite : Roo.HtmlEditorCore.cwhite;
1089 var cblack = typeof(ed.cblack) != 'undefined' && ed.cwhite !== false ? ed.cblack : Roo.HtmlEditorCore.cblack;
1092 var parts = v.split(/;/);
1095 Roo.each(parts, function(p) {
1096 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1100 var l = p.split(':').shift().replace(/\s+/g,'');
1101 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1103 if ( cblack.indexOf(l) > -1) {
1104 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1105 //node.removeAttribute(n);
1109 // only allow 'c whitelisted system attributes'
1110 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1111 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1112 //node.removeAttribute(n);
1123 node.setAttribute(n, clean.join(';'));
1125 node.removeAttribute(n);
1131 for (var i = node.attributes.length-1; i > -1 ; i--) {
1132 var a = node.attributes[i];
1135 if (a.name.toLowerCase().substr(0,2)=='on') {
1136 node.removeAttribute(a.name);
1139 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1140 node.removeAttribute(a.name);
1143 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1144 cleanAttr(a.name,a.value); // fixme..
1147 if (a.name == 'style') {
1148 cleanStyle(a.name,a.value);
1151 /// clean up MS crap..
1152 // tecnically this should be a list of valid class'es..
1155 if (a.name == 'class') {
1156 if (a.value.match(/^Mso/)) {
1157 node.className = '';
1160 if (a.value.match(/body/)) {
1161 node.className = '';
1172 this.cleanUpChildren(node);
1177 * Clean up MS wordisms...
1179 cleanWord : function(node)
1182 var cleanWordChildren = function()
1184 if (!node.childNodes.length) {
1187 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1188 _t.cleanWord(node.childNodes[i]);
1194 this.cleanWord(this.doc.body);
1197 if (node.nodeName == "#text") {
1198 // clean up silly Windows -- stuff?
1201 if (node.nodeName == "#comment") {
1202 node.parentNode.removeChild(node);
1203 // clean up silly Windows -- stuff?
1207 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1208 node.parentNode.removeChild(node);
1212 // remove - but keep children..
1213 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1214 while (node.childNodes.length) {
1215 var cn = node.childNodes[0];
1216 node.removeChild(cn);
1217 node.parentNode.insertBefore(cn, node);
1219 node.parentNode.removeChild(node);
1220 cleanWordChildren();
1224 if (node.className.length) {
1226 var cn = node.className.split(/\W+/);
1228 Roo.each(cn, function(cls) {
1229 if (cls.match(/Mso[a-zA-Z]+/)) {
1234 node.className = cna.length ? cna.join(' ') : '';
1236 node.removeAttribute("class");
1240 if (node.hasAttribute("lang")) {
1241 node.removeAttribute("lang");
1244 if (node.hasAttribute("style")) {
1246 var styles = node.getAttribute("style").split(";");
1248 Roo.each(styles, function(s) {
1249 if (!s.match(/:/)) {
1252 var kv = s.split(":");
1253 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1256 // what ever is left... we allow.
1259 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1260 if (!nstyle.length) {
1261 node.removeAttribute('style');
1265 cleanWordChildren();
1269 domToHTML : function(currentElement, depth, nopadtext) {
1272 nopadtext = nopadtext || false;
1274 if (!currentElement) {
1275 return this.domToHTML(this.doc.body);
1278 //Roo.log(currentElement);
1280 var allText = false;
1281 var nodeName = currentElement.nodeName;
1282 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1284 if (nodeName == '#text') {
1285 return currentElement.nodeValue;
1290 if (nodeName != 'BODY') {
1293 // Prints the node tagName, such as <A>, <IMG>, etc
1296 for(i = 0; i < currentElement.attributes.length;i++) {
1298 var aname = currentElement.attributes.item(i).name;
1299 if (!currentElement.attributes.item(i).value.length) {
1302 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1305 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1314 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1317 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1322 // Traverse the tree
1324 var currentElementChild = currentElement.childNodes.item(i);
1328 while (currentElementChild) {
1329 // Formatting code (indent the tree so it looks nice on the screen)
1330 var nopad = nopadtext;
1331 if (lastnode == 'SPAN') {
1335 if (currentElementChild.nodeName == '#text') {
1336 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1337 if (!nopad && toadd.length > 80) {
1338 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1343 currentElementChild = currentElement.childNodes.item(i);
1349 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1351 // Recursively traverse the tree structure of the child node
1352 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1353 lastnode = currentElementChild.nodeName;
1355 currentElementChild=currentElement.childNodes.item(i);
1361 // The remaining code is mostly for formatting the tree
1362 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1367 ret+= "</"+tagName+">";
1373 // hide stuff that is not compatible
1391 * @cfg {String} fieldClass @hide
1394 * @cfg {String} focusClass @hide
1397 * @cfg {String} autoCreate @hide
1400 * @cfg {String} inputType @hide
1403 * @cfg {String} invalidClass @hide
1406 * @cfg {String} invalidText @hide
1409 * @cfg {String} msgFx @hide
1412 * @cfg {String} validateOnBlur @hide
1416 Roo.HtmlEditorCore.white = [
1417 'area', 'br', 'img', 'input', 'hr', 'wbr',
1419 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1420 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1421 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1422 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1423 'table', 'ul', 'xmp',
1425 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1428 'dir', 'menu', 'ol', 'ul', 'dl',
1434 Roo.HtmlEditorCore.black = [
1435 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1437 'base', 'basefont', 'bgsound', 'blink', 'body',
1438 'frame', 'frameset', 'head', 'html', 'ilayer',
1439 'iframe', 'layer', 'link', 'meta', 'object',
1440 'script', 'style' ,'title', 'xml' // clean later..
1442 Roo.HtmlEditorCore.clean = [
1443 'script', 'style', 'title', 'xml'
1445 Roo.HtmlEditorCore.remove = [
1450 Roo.HtmlEditorCore.ablack = [
1454 Roo.HtmlEditorCore.aclean = [
1455 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1459 Roo.HtmlEditorCore.pwhite= [
1460 'http', 'https', 'mailto'
1463 // white listed style attributes.
1464 Roo.HtmlEditorCore.cwhite= [
1465 // 'text-align', /// default is to allow most things..
1471 // black listed style attributes.
1472 Roo.HtmlEditorCore.cblack= [
1473 // 'font-size' -- this can be set by the project
1477 Roo.HtmlEditorCore.swapCodes =[