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;
392 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
396 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
397 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
402 initEditor : function(){
403 //console.log("INIT EDITOR");
408 this.doc.designMode="on";
410 this.doc.write(this.getDocMarkup());
413 var dbody = (this.doc.body || this.doc.documentElement);
414 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
415 // this copies styles from the containing element into thsi one..
416 // not sure why we need all of this..
417 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
419 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
420 //ss['background-attachment'] = 'fixed'; // w3c
421 dbody.bgProperties = 'fixed'; // ie
422 //Roo.DomHelper.applyStyles(dbody, ss);
423 Roo.EventManager.on(this.doc, {
424 //'mousedown': this.onEditorEvent,
425 'mouseup': this.onEditorEvent,
426 'dblclick': this.onEditorEvent,
427 'click': this.onEditorEvent,
428 'keyup': this.onEditorEvent,
433 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
435 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
436 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
438 this.initialized = true;
440 this.owner.fireEvent('initialize', this);
445 onDestroy : function(){
451 //for (var i =0; i < this.toolbars.length;i++) {
452 // // fixme - ask toolbars for heights?
453 // this.toolbars[i].onDestroy();
456 //this.wrap.dom.innerHTML = '';
457 //this.wrap.remove();
462 onFirstFocus : function(){
467 this.activated = true;
470 if(Roo.isGecko){ // prevent silly gecko errors
472 var s = this.win.getSelection();
473 if(!s.focusNode || s.focusNode.nodeType != 3){
474 var r = s.getRangeAt(0);
475 r.selectNodeContents((this.doc.body || this.doc.documentElement));
480 this.execCmd('useCSS', true);
481 this.execCmd('styleWithCSS', false);
484 this.owner.fireEvent('activate', this);
488 adjustFont: function(btn){
489 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
490 //if(Roo.isSafari){ // safari
493 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
494 if(Roo.isSafari){ // safari
495 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
496 v = (v < 10) ? 10 : v;
497 v = (v > 48) ? 48 : v;
498 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
503 v = Math.max(1, v+adjust);
505 this.execCmd('FontSize', v );
508 onEditorEvent : function(e){
509 this.owner.fireEvent('editorevent', this, e);
510 // this.updateToolbar();
511 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
514 insertTag : function(tg)
516 // could be a bit smarter... -> wrap the current selected tRoo..
517 if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
519 range = this.createRange(this.getSelection());
520 var wrappingNode = this.doc.createElement(tg.toLowerCase());
521 wrappingNode.appendChild(range.extractContents());
522 range.insertNode(wrappingNode);
529 this.execCmd("formatblock", tg);
533 insertText : function(txt)
537 var range = this.createRange();
538 range.deleteContents();
539 //alert(Sender.getAttribute('label'));
541 range.insertNode(this.doc.createTextNode(txt));
547 * Executes a Midas editor command on the editor document and performs necessary focus and
548 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
549 * @param {String} cmd The Midas command
550 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
552 relayCmd : function(cmd, value){
554 this.execCmd(cmd, value);
555 this.owner.fireEvent('editorevent', this);
556 //this.updateToolbar();
557 this.owner.deferFocus();
561 * Executes a Midas editor command directly on the editor document.
562 * For visual commands, you should use {@link #relayCmd} instead.
563 * <b>This should only be called after the editor is initialized.</b>
564 * @param {String} cmd The Midas command
565 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
567 execCmd : function(cmd, value){
568 this.doc.execCommand(cmd, false, value === undefined ? null : value);
575 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
577 * @param {String} text | dom node..
579 insertAtCursor : function(text)
590 var r = this.doc.selection.createRange();
601 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
605 // from jquery ui (MIT licenced)
609 if (win.getSelection && win.getSelection().getRangeAt) {
610 range = win.getSelection().getRangeAt(0);
611 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
612 range.insertNode(node);
613 } else if (win.document.selection && win.document.selection.createRange) {
614 // no firefox support
615 var txt = typeof(text) == 'string' ? text : text.outerHTML;
616 win.document.selection.createRange().pasteHTML(txt);
618 // no firefox support
619 var txt = typeof(text) == 'string' ? text : text.outerHTML;
620 this.execCmd('InsertHTML', txt);
629 mozKeyPress : function(e){
631 var c = e.getCharCode(), cmd;
634 c = String.fromCharCode(c).toLowerCase();
648 this.cleanUpPaste.defer(100, this);
664 fixKeys : function(){ // load time branching for fastest keydown performance
667 var k = e.getKey(), r;
670 r = this.doc.selection.createRange();
673 r.pasteHTML('    ');
680 r = this.doc.selection.createRange();
682 var target = r.parentElement();
683 if(!target || target.tagName.toLowerCase() != 'li'){
685 r.pasteHTML('<br />');
691 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
692 this.cleanUpPaste.defer(100, this);
698 }else if(Roo.isOpera){
704 this.execCmd('InsertHTML','    ');
707 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
708 this.cleanUpPaste.defer(100, this);
713 }else if(Roo.isSafari){
719 this.execCmd('InsertText','\t');
723 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
724 this.cleanUpPaste.defer(100, this);
732 getAllAncestors: function()
734 var p = this.getSelectedNode();
737 a.push(p); // push blank onto stack..
738 p = this.getParentElement();
742 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
746 a.push(this.doc.body);
753 getSelection : function()
756 return Roo.isIE ? this.doc.selection : this.win.getSelection();
759 getSelectedNode: function()
761 // this may only work on Gecko!!!
763 // should we cache this!!!!
768 var range = this.createRange(this.getSelection()).cloneRange();
771 var parent = range.parentElement();
773 var testRange = range.duplicate();
774 testRange.moveToElementText(parent);
775 if (testRange.inRange(range)) {
778 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
781 parent = parent.parentElement;
786 // is ancestor a text element.
787 var ac = range.commonAncestorContainer;
788 if (ac.nodeType == 3) {
792 var ar = ac.childNodes;
795 var other_nodes = [];
796 var has_other_nodes = false;
797 for (var i=0;i<ar.length;i++) {
798 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
801 // fullly contained node.
803 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
808 // probably selected..
809 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
810 other_nodes.push(ar[i]);
814 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
819 has_other_nodes = true;
821 if (!nodes.length && other_nodes.length) {
824 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
830 createRange: function(sel)
832 // this has strange effects when using with
833 // top toolbar - not sure if it's a great idea.
834 //this.editor.contentWindow.focus();
835 if (typeof sel != "undefined") {
837 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
839 return this.doc.createRange();
842 return this.doc.createRange();
845 getParentElement: function()
849 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
851 var range = this.createRange(sel);
854 var p = range.commonAncestorContainer;
855 while (p.nodeType == 3) { // text node
866 * Range intersection.. the hard stuff...
870 * [ -- selected range --- ]
874 * if end is before start or hits it. fail.
875 * if start is after end or hits it fail.
877 * if either hits (but other is outside. - then it's not
883 // @see http://www.thismuchiknow.co.uk/?p=64.
884 rangeIntersectsNode : function(range, node)
886 var nodeRange = node.ownerDocument.createRange();
888 nodeRange.selectNode(node);
890 nodeRange.selectNodeContents(node);
893 var rangeStartRange = range.cloneRange();
894 rangeStartRange.collapse(true);
896 var rangeEndRange = range.cloneRange();
897 rangeEndRange.collapse(false);
899 var nodeStartRange = nodeRange.cloneRange();
900 nodeStartRange.collapse(true);
902 var nodeEndRange = nodeRange.cloneRange();
903 nodeEndRange.collapse(false);
905 return rangeStartRange.compareBoundaryPoints(
906 Range.START_TO_START, nodeEndRange) == -1 &&
907 rangeEndRange.compareBoundaryPoints(
908 Range.START_TO_START, nodeStartRange) == 1;
912 rangeCompareNode : function(range, node)
914 var nodeRange = node.ownerDocument.createRange();
916 nodeRange.selectNode(node);
918 nodeRange.selectNodeContents(node);
922 range.collapse(true);
924 nodeRange.collapse(true);
926 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
927 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
929 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
931 var nodeIsBefore = ss == 1;
932 var nodeIsAfter = ee == -1;
934 if (nodeIsBefore && nodeIsAfter)
936 if (!nodeIsBefore && nodeIsAfter)
937 return 1; //right trailed.
939 if (nodeIsBefore && !nodeIsAfter)
940 return 2; // left trailed.
945 // private? - in a new class?
946 cleanUpPaste : function()
948 // cleans up the whole document..
949 Roo.log('cleanuppaste');
951 this.cleanUpChildren(this.doc.body);
952 var clean = this.cleanWordChars(this.doc.body.innerHTML);
953 if (clean != this.doc.body.innerHTML) {
954 this.doc.body.innerHTML = clean;
959 cleanWordChars : function(input) {// change the chars to hex code
960 var he = Roo.HtmlEditorCore;
963 Roo.each(he.swapCodes, function(sw) {
964 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
966 output = output.replace(swapper, sw[1]);
973 cleanUpChildren : function (n)
975 if (!n.childNodes.length) {
978 for (var i = n.childNodes.length-1; i > -1 ; i--) {
979 this.cleanUpChild(n.childNodes[i]);
986 cleanUpChild : function (node)
990 if (node.nodeName == "#text") {
991 // clean up silly Windows -- stuff?
994 if (node.nodeName == "#comment") {
995 node.parentNode.removeChild(node);
996 // clean up silly Windows -- stuff?
1000 if (Roo.HtmlEditorCore.black.indexOf(node.tagName.toLowerCase()) > -1 && this.clearUp) {
1002 node.parentNode.removeChild(node);
1007 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1009 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1010 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1012 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1013 // remove_keep_children = true;
1016 if (remove_keep_children) {
1017 this.cleanUpChildren(node);
1018 // inserts everything just before this node...
1019 while (node.childNodes.length) {
1020 var cn = node.childNodes[0];
1021 node.removeChild(cn);
1022 node.parentNode.insertBefore(cn, node);
1024 node.parentNode.removeChild(node);
1028 if (!node.attributes || !node.attributes.length) {
1029 this.cleanUpChildren(node);
1033 function cleanAttr(n,v)
1036 if (v.match(/^\./) || v.match(/^\//)) {
1039 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1042 if (v.match(/^#/)) {
1045 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1046 node.removeAttribute(n);
1050 function cleanStyle(n,v)
1052 if (v.match(/expression/)) { //XSS?? should we even bother..
1053 node.removeAttribute(n);
1056 var cwhite = typeof(ed.cwhite) != 'undefined' && ed.cwhite !== false ? ed.cwhite : Roo.HtmlEditorCore.cwhite;
1057 var cblack = typeof(ed.cblack) != 'undefined' && ed.cwhite !== false ? ed.cblack : Roo.HtmlEditorCore.cblack;
1060 var parts = v.split(/;/);
1063 Roo.each(parts, function(p) {
1064 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1068 var l = p.split(':').shift().replace(/\s+/g,'');
1069 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1071 if ( cblack.indexOf(l) > -1) {
1072 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1073 //node.removeAttribute(n);
1077 // only allow 'c whitelisted system attributes'
1078 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1079 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1080 //node.removeAttribute(n);
1091 node.setAttribute(n, clean.join(';'));
1093 node.removeAttribute(n);
1099 for (var i = node.attributes.length-1; i > -1 ; i--) {
1100 var a = node.attributes[i];
1103 if (a.name.toLowerCase().substr(0,2)=='on') {
1104 node.removeAttribute(a.name);
1107 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1108 node.removeAttribute(a.name);
1111 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1112 cleanAttr(a.name,a.value); // fixme..
1115 if (a.name == 'style') {
1116 cleanStyle(a.name,a.value);
1119 /// clean up MS crap..
1120 // tecnically this should be a list of valid class'es..
1123 if (a.name == 'class') {
1124 if (a.value.match(/^Mso/)) {
1125 node.className = '';
1128 if (a.value.match(/body/)) {
1129 node.className = '';
1140 this.cleanUpChildren(node);
1145 * Clean up MS wordisms...
1147 cleanWord : function(node)
1150 var cleanWordChildren = function()
1152 if (!node.childNodes.length) {
1155 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1156 _t.cleanWord(node.childNodes[i]);
1162 this.cleanWord(this.doc.body);
1165 if (node.nodeName == "#text") {
1166 // clean up silly Windows -- stuff?
1169 if (node.nodeName == "#comment") {
1170 node.parentNode.removeChild(node);
1171 // clean up silly Windows -- stuff?
1175 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1176 node.parentNode.removeChild(node);
1180 // remove - but keep children..
1181 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1182 while (node.childNodes.length) {
1183 var cn = node.childNodes[0];
1184 node.removeChild(cn);
1185 node.parentNode.insertBefore(cn, node);
1187 node.parentNode.removeChild(node);
1188 cleanWordChildren();
1192 if (node.className.length) {
1194 var cn = node.className.split(/\W+/);
1196 Roo.each(cn, function(cls) {
1197 if (cls.match(/Mso[a-zA-Z]+/)) {
1202 node.className = cna.length ? cna.join(' ') : '';
1204 node.removeAttribute("class");
1208 if (node.hasAttribute("lang")) {
1209 node.removeAttribute("lang");
1212 if (node.hasAttribute("style")) {
1214 var styles = node.getAttribute("style").split(";");
1216 Roo.each(styles, function(s) {
1217 if (!s.match(/:/)) {
1220 var kv = s.split(":");
1221 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1224 // what ever is left... we allow.
1227 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1228 if (!nstyle.length) {
1229 node.removeAttribute('style');
1233 cleanWordChildren();
1237 domToHTML : function(currentElement, depth, nopadtext) {
1240 nopadtext = nopadtext || false;
1242 if (!currentElement) {
1243 return this.domToHTML(this.doc.body);
1246 //Roo.log(currentElement);
1248 var allText = false;
1249 var nodeName = currentElement.nodeName;
1250 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1252 if (nodeName == '#text') {
1253 return currentElement.nodeValue;
1258 if (nodeName != 'BODY') {
1261 // Prints the node tagName, such as <A>, <IMG>, etc
1264 for(i = 0; i < currentElement.attributes.length;i++) {
1266 var aname = currentElement.attributes.item(i).name;
1267 if (!currentElement.attributes.item(i).value.length) {
1270 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1273 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1282 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1285 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1290 // Traverse the tree
1292 var currentElementChild = currentElement.childNodes.item(i);
1296 while (currentElementChild) {
1297 // Formatting code (indent the tree so it looks nice on the screen)
1298 var nopad = nopadtext;
1299 if (lastnode == 'SPAN') {
1303 if (currentElementChild.nodeName == '#text') {
1304 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1305 if (!nopad && toadd.length > 80) {
1306 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1311 currentElementChild = currentElement.childNodes.item(i);
1317 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1319 // Recursively traverse the tree structure of the child node
1320 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1321 lastnode = currentElementChild.nodeName;
1323 currentElementChild=currentElement.childNodes.item(i);
1329 // The remaining code is mostly for formatting the tree
1330 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1335 ret+= "</"+tagName+">";
1341 // hide stuff that is not compatible
1359 * @cfg {String} fieldClass @hide
1362 * @cfg {String} focusClass @hide
1365 * @cfg {String} autoCreate @hide
1368 * @cfg {String} inputType @hide
1371 * @cfg {String} invalidClass @hide
1374 * @cfg {String} invalidText @hide
1377 * @cfg {String} msgFx @hide
1380 * @cfg {String} validateOnBlur @hide
1384 Roo.HtmlEditorCore.white = [
1385 'area', 'br', 'img', 'input', 'hr', 'wbr',
1387 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1388 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1389 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1390 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1391 'table', 'ul', 'xmp',
1393 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1396 'dir', 'menu', 'ol', 'ul', 'dl',
1402 Roo.HtmlEditorCore.black = [
1403 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1405 'base', 'basefont', 'bgsound', 'blink', 'body',
1406 'frame', 'frameset', 'head', 'html', 'ilayer',
1407 'iframe', 'layer', 'link', 'meta', 'object',
1408 'script', 'style' ,'title', 'xml' // clean later..
1410 Roo.HtmlEditorCore.clean = [
1411 'script', 'style', 'title', 'xml'
1413 Roo.HtmlEditorCore.remove = [
1418 Roo.HtmlEditorCore.ablack = [
1422 Roo.HtmlEditorCore.aclean = [
1423 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1427 Roo.HtmlEditorCore.pwhite= [
1428 'http', 'https', 'mailto'
1431 // white listed style attributes.
1432 Roo.HtmlEditorCore.cwhite= [
1433 // 'text-align', /// default is to allow most things..
1439 // black listed style attributes.
1440 Roo.HtmlEditorCore.cblack= [
1441 // 'font-size' -- this can be set by the project
1445 Roo.HtmlEditorCore.swapCodes =[