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,
130 // blacklist + whitelisted elements..
137 * Protected method that will not generally be called directly. It
138 * is called when the editor initializes the iframe with HTML contents. Override this method if you
139 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
141 getDocMarkup : function(){
144 Roo.log(this.stylesheets);
146 // inherit styels from page...??
147 if (this.stylesheets === false) {
149 Roo.get(document.head).select('style').each(function(node) {
150 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
153 Roo.get(document.head).select('link').each(function(node) {
154 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
157 } else if (!this.stylesheets.length) {
159 st = '<style type="text/css">' +
160 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
163 Roo.each(this.stylesheets, function(s) {
164 st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
169 st += '<style type="text/css">' +
170 'IMG { cursor: pointer } ' +
174 return '<html><head>' + st +
175 //<style type="text/css">' +
176 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
178 ' </head><body class="roo-htmleditor-body"></body></html>';
182 onRender : function(ct, position)
185 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
186 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
189 this.el.dom.style.border = '0 none';
190 this.el.dom.setAttribute('tabIndex', -1);
191 this.el.addClass('x-hidden hide');
195 if(Roo.isIE){ // fix IE 1px bogus margin
196 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
200 this.frameId = Roo.id();
204 var iframe = this.owner.wrap.createChild({
206 cls: 'form-control', // bootstrap..
210 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
215 this.iframe = iframe.dom;
219 this.doc.designMode = 'on';
222 this.doc.write(this.getDocMarkup());
226 var task = { // must defer to wait for browser to be ready
228 //console.log("run task?" + this.doc.readyState);
230 if(this.doc.body || this.doc.readyState == 'complete'){
232 this.doc.designMode="on";
236 Roo.TaskMgr.stop(task);
237 this.initEditor.defer(10, this);
244 Roo.TaskMgr.start(task);
251 onResize : function(w, h)
253 Roo.log('resize: ' +w + ',' + h );
254 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
258 if(typeof w == 'number'){
260 this.iframe.style.width = w + 'px';
262 if(typeof h == 'number'){
264 this.iframe.style.height = h + 'px';
266 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
273 * Toggles the editor between standard and source edit mode.
274 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
276 toggleSourceEdit : function(sourceEditMode){
278 this.sourceEditMode = sourceEditMode === true;
280 if(this.sourceEditMode){
282 Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
285 Roo.get(this.iframe).removeClass(['x-hidden','hide']);
286 //this.iframe.className = '';
289 //this.setSize(this.owner.wrap.getSize());
290 //this.fireEvent('editmodechange', this, this.sourceEditMode);
297 * Protected method that will not generally be called directly. If you need/want
298 * custom HTML cleanup, this is the method you should override.
299 * @param {String} html The HTML to be cleaned
300 * return {String} The cleaned HTML
302 cleanHtml : function(html){
305 if(Roo.isSafari){ // strip safari nonsense
306 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
309 if(html == ' '){
316 * HTML Editor -> Textarea
317 * Protected method that will not generally be called directly. Syncs the contents
318 * of the editor iframe with the textarea.
320 syncValue : function(){
321 if(this.initialized){
322 var bd = (this.doc.body || this.doc.documentElement);
323 //this.cleanUpPaste(); -- this is done else where and causes havoc..
324 var html = bd.innerHTML;
326 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
327 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
329 html = '<div style="'+m[0]+'">' + html + '</div>';
332 html = this.cleanHtml(html);
333 // fix up the special chars.. normaly like back quotes in word...
334 // however we do not want to do this with chinese..
335 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
336 var cc = b.charCodeAt();
338 (cc >= 0x4E00 && cc < 0xA000 ) ||
339 (cc >= 0x3400 && cc < 0x4E00 ) ||
340 (cc >= 0xf900 && cc < 0xfb00 )
346 if(this.owner.fireEvent('beforesync', this, html) !== false){
347 this.el.dom.value = html;
348 this.owner.fireEvent('sync', this, html);
354 * Protected method that will not generally be called directly. Pushes the value of the textarea
355 * into the iframe editor.
357 pushValue : function(){
358 if(this.initialized){
359 var v = this.el.dom.value.trim();
365 if(this.owner.fireEvent('beforepush', this, v) !== false){
366 var d = (this.doc.body || this.doc.documentElement);
369 this.el.dom.value = d.innerHTML;
370 this.owner.fireEvent('push', this, v);
376 deferFocus : function(){
377 this.focus.defer(10, this);
382 if(this.win && !this.sourceEditMode){
389 assignDocWin: function()
391 var iframe = this.iframe;
394 this.doc = iframe.contentWindow.document;
395 this.win = iframe.contentWindow;
397 // if (!Roo.get(this.frameId)) {
400 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
401 // this.win = Roo.get(this.frameId).dom.contentWindow;
403 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
407 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
408 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
413 initEditor : function(){
414 //console.log("INIT EDITOR");
419 this.doc.designMode="on";
421 this.doc.write(this.getDocMarkup());
424 var dbody = (this.doc.body || this.doc.documentElement);
425 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
426 // this copies styles from the containing element into thsi one..
427 // not sure why we need all of this..
428 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
430 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
431 //ss['background-attachment'] = 'fixed'; // w3c
432 dbody.bgProperties = 'fixed'; // ie
433 //Roo.DomHelper.applyStyles(dbody, ss);
434 Roo.EventManager.on(this.doc, {
435 //'mousedown': this.onEditorEvent,
436 'mouseup': this.onEditorEvent,
437 'dblclick': this.onEditorEvent,
438 'click': this.onEditorEvent,
439 'keyup': this.onEditorEvent,
444 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
446 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
447 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
449 this.initialized = true;
451 this.owner.fireEvent('initialize', this);
456 onDestroy : function(){
462 //for (var i =0; i < this.toolbars.length;i++) {
463 // // fixme - ask toolbars for heights?
464 // this.toolbars[i].onDestroy();
467 //this.wrap.dom.innerHTML = '';
468 //this.wrap.remove();
473 onFirstFocus : function(){
478 this.activated = true;
481 if(Roo.isGecko){ // prevent silly gecko errors
483 var s = this.win.getSelection();
484 if(!s.focusNode || s.focusNode.nodeType != 3){
485 var r = s.getRangeAt(0);
486 r.selectNodeContents((this.doc.body || this.doc.documentElement));
491 this.execCmd('useCSS', true);
492 this.execCmd('styleWithCSS', false);
495 this.owner.fireEvent('activate', this);
499 adjustFont: function(btn){
500 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
501 //if(Roo.isSafari){ // safari
504 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
505 if(Roo.isSafari){ // safari
506 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
507 v = (v < 10) ? 10 : v;
508 v = (v > 48) ? 48 : v;
509 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
514 v = Math.max(1, v+adjust);
516 this.execCmd('FontSize', v );
519 onEditorEvent : function(e){
520 this.owner.fireEvent('editorevent', this, e);
521 // this.updateToolbar();
522 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
525 insertTag : function(tg)
527 // could be a bit smarter... -> wrap the current selected tRoo..
528 if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
530 range = this.createRange(this.getSelection());
531 var wrappingNode = this.doc.createElement(tg.toLowerCase());
532 wrappingNode.appendChild(range.extractContents());
533 range.insertNode(wrappingNode);
540 this.execCmd("formatblock", tg);
544 insertText : function(txt)
548 var range = this.createRange();
549 range.deleteContents();
550 //alert(Sender.getAttribute('label'));
552 range.insertNode(this.doc.createTextNode(txt));
558 * Executes a Midas editor command on the editor document and performs necessary focus and
559 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
560 * @param {String} cmd The Midas command
561 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
563 relayCmd : function(cmd, value){
565 this.execCmd(cmd, value);
566 this.owner.fireEvent('editorevent', this);
567 //this.updateToolbar();
568 this.owner.deferFocus();
572 * Executes a Midas editor command directly on the editor document.
573 * For visual commands, you should use {@link #relayCmd} instead.
574 * <b>This should only be called after the editor is initialized.</b>
575 * @param {String} cmd The Midas command
576 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
578 execCmd : function(cmd, value){
579 this.doc.execCommand(cmd, false, value === undefined ? null : value);
586 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
588 * @param {String} text | dom node..
590 insertAtCursor : function(text)
601 var r = this.doc.selection.createRange();
612 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
616 // from jquery ui (MIT licenced)
620 if (win.getSelection && win.getSelection().getRangeAt) {
621 range = win.getSelection().getRangeAt(0);
622 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
623 range.insertNode(node);
624 } else if (win.document.selection && win.document.selection.createRange) {
625 // no firefox support
626 var txt = typeof(text) == 'string' ? text : text.outerHTML;
627 win.document.selection.createRange().pasteHTML(txt);
629 // no firefox support
630 var txt = typeof(text) == 'string' ? text : text.outerHTML;
631 this.execCmd('InsertHTML', txt);
640 mozKeyPress : function(e){
642 var c = e.getCharCode(), cmd;
645 c = String.fromCharCode(c).toLowerCase();
659 this.cleanUpPaste.defer(100, this);
675 fixKeys : function(){ // load time branching for fastest keydown performance
678 var k = e.getKey(), r;
681 r = this.doc.selection.createRange();
684 r.pasteHTML('    ');
691 r = this.doc.selection.createRange();
693 var target = r.parentElement();
694 if(!target || target.tagName.toLowerCase() != 'li'){
696 r.pasteHTML('<br />');
702 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
703 this.cleanUpPaste.defer(100, this);
709 }else if(Roo.isOpera){
715 this.execCmd('InsertHTML','    ');
718 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
719 this.cleanUpPaste.defer(100, this);
724 }else if(Roo.isSafari){
730 this.execCmd('InsertText','\t');
734 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
735 this.cleanUpPaste.defer(100, this);
743 getAllAncestors: function()
745 var p = this.getSelectedNode();
748 a.push(p); // push blank onto stack..
749 p = this.getParentElement();
753 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
757 a.push(this.doc.body);
764 getSelection : function()
767 return Roo.isIE ? this.doc.selection : this.win.getSelection();
770 getSelectedNode: function()
772 // this may only work on Gecko!!!
774 // should we cache this!!!!
779 var range = this.createRange(this.getSelection()).cloneRange();
782 var parent = range.parentElement();
784 var testRange = range.duplicate();
785 testRange.moveToElementText(parent);
786 if (testRange.inRange(range)) {
789 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
792 parent = parent.parentElement;
797 // is ancestor a text element.
798 var ac = range.commonAncestorContainer;
799 if (ac.nodeType == 3) {
803 var ar = ac.childNodes;
806 var other_nodes = [];
807 var has_other_nodes = false;
808 for (var i=0;i<ar.length;i++) {
809 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
812 // fullly contained node.
814 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
819 // probably selected..
820 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
821 other_nodes.push(ar[i]);
825 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
830 has_other_nodes = true;
832 if (!nodes.length && other_nodes.length) {
835 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
841 createRange: function(sel)
843 // this has strange effects when using with
844 // top toolbar - not sure if it's a great idea.
845 //this.editor.contentWindow.focus();
846 if (typeof sel != "undefined") {
848 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
850 return this.doc.createRange();
853 return this.doc.createRange();
856 getParentElement: function()
860 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
862 var range = this.createRange(sel);
865 var p = range.commonAncestorContainer;
866 while (p.nodeType == 3) { // text node
877 * Range intersection.. the hard stuff...
881 * [ -- selected range --- ]
885 * if end is before start or hits it. fail.
886 * if start is after end or hits it fail.
888 * if either hits (but other is outside. - then it's not
894 // @see http://www.thismuchiknow.co.uk/?p=64.
895 rangeIntersectsNode : function(range, node)
897 var nodeRange = node.ownerDocument.createRange();
899 nodeRange.selectNode(node);
901 nodeRange.selectNodeContents(node);
904 var rangeStartRange = range.cloneRange();
905 rangeStartRange.collapse(true);
907 var rangeEndRange = range.cloneRange();
908 rangeEndRange.collapse(false);
910 var nodeStartRange = nodeRange.cloneRange();
911 nodeStartRange.collapse(true);
913 var nodeEndRange = nodeRange.cloneRange();
914 nodeEndRange.collapse(false);
916 return rangeStartRange.compareBoundaryPoints(
917 Range.START_TO_START, nodeEndRange) == -1 &&
918 rangeEndRange.compareBoundaryPoints(
919 Range.START_TO_START, nodeStartRange) == 1;
923 rangeCompareNode : function(range, node)
925 var nodeRange = node.ownerDocument.createRange();
927 nodeRange.selectNode(node);
929 nodeRange.selectNodeContents(node);
933 range.collapse(true);
935 nodeRange.collapse(true);
937 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
938 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
940 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
942 var nodeIsBefore = ss == 1;
943 var nodeIsAfter = ee == -1;
945 if (nodeIsBefore && nodeIsAfter)
947 if (!nodeIsBefore && nodeIsAfter)
948 return 1; //right trailed.
950 if (nodeIsBefore && !nodeIsAfter)
951 return 2; // left trailed.
956 // private? - in a new class?
957 cleanUpPaste : function()
959 // cleans up the whole document..
960 Roo.log('cleanuppaste');
962 this.cleanUpChildren(this.doc.body);
963 var clean = this.cleanWordChars(this.doc.body.innerHTML);
964 if (clean != this.doc.body.innerHTML) {
965 this.doc.body.innerHTML = clean;
970 cleanWordChars : function(input) {// change the chars to hex code
971 var he = Roo.HtmlEditorCore;
974 Roo.each(he.swapCodes, function(sw) {
975 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
977 output = output.replace(swapper, sw[1]);
984 cleanUpChildren : function (n)
986 if (!n.childNodes.length) {
989 for (var i = n.childNodes.length-1; i > -1 ; i--) {
990 this.cleanUpChild(n.childNodes[i]);
997 cleanUpChild : function (node)
1000 //console.log(node);
1001 if (node.nodeName == "#text") {
1002 // clean up silly Windows -- stuff?
1005 if (node.nodeName == "#comment") {
1006 node.parentNode.removeChild(node);
1007 // clean up silly Windows -- stuff?
1011 if (Roo.HtmlEditorCore.black.indexOf(node.tagName.toLowerCase()) > -1 && this.clearUp) {
1013 node.parentNode.removeChild(node);
1018 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1020 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1021 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1023 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1024 // remove_keep_children = true;
1027 if (remove_keep_children) {
1028 this.cleanUpChildren(node);
1029 // inserts everything just before this node...
1030 while (node.childNodes.length) {
1031 var cn = node.childNodes[0];
1032 node.removeChild(cn);
1033 node.parentNode.insertBefore(cn, node);
1035 node.parentNode.removeChild(node);
1039 if (!node.attributes || !node.attributes.length) {
1040 this.cleanUpChildren(node);
1044 function cleanAttr(n,v)
1047 if (v.match(/^\./) || v.match(/^\//)) {
1050 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1053 if (v.match(/^#/)) {
1056 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1057 node.removeAttribute(n);
1061 function cleanStyle(n,v)
1063 if (v.match(/expression/)) { //XSS?? should we even bother..
1064 node.removeAttribute(n);
1067 var cwhite = typeof(ed.cwhite) != 'undefined' && ed.cwhite !== false ? ed.cwhite : Roo.HtmlEditorCore.cwhite;
1068 var cblack = typeof(ed.cblack) != 'undefined' && ed.cwhite !== false ? ed.cblack : Roo.HtmlEditorCore.cblack;
1071 var parts = v.split(/;/);
1074 Roo.each(parts, function(p) {
1075 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1079 var l = p.split(':').shift().replace(/\s+/g,'');
1080 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1082 if ( cblack.indexOf(l) > -1) {
1083 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1084 //node.removeAttribute(n);
1088 // only allow 'c whitelisted system attributes'
1089 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1090 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1091 //node.removeAttribute(n);
1102 node.setAttribute(n, clean.join(';'));
1104 node.removeAttribute(n);
1110 for (var i = node.attributes.length-1; i > -1 ; i--) {
1111 var a = node.attributes[i];
1114 if (a.name.toLowerCase().substr(0,2)=='on') {
1115 node.removeAttribute(a.name);
1118 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1119 node.removeAttribute(a.name);
1122 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1123 cleanAttr(a.name,a.value); // fixme..
1126 if (a.name == 'style') {
1127 cleanStyle(a.name,a.value);
1130 /// clean up MS crap..
1131 // tecnically this should be a list of valid class'es..
1134 if (a.name == 'class') {
1135 if (a.value.match(/^Mso/)) {
1136 node.className = '';
1139 if (a.value.match(/body/)) {
1140 node.className = '';
1151 this.cleanUpChildren(node);
1156 * Clean up MS wordisms...
1158 cleanWord : function(node)
1161 var cleanWordChildren = function()
1163 if (!node.childNodes.length) {
1166 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1167 _t.cleanWord(node.childNodes[i]);
1173 this.cleanWord(this.doc.body);
1176 if (node.nodeName == "#text") {
1177 // clean up silly Windows -- stuff?
1180 if (node.nodeName == "#comment") {
1181 node.parentNode.removeChild(node);
1182 // clean up silly Windows -- stuff?
1186 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1187 node.parentNode.removeChild(node);
1191 // remove - but keep children..
1192 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1193 while (node.childNodes.length) {
1194 var cn = node.childNodes[0];
1195 node.removeChild(cn);
1196 node.parentNode.insertBefore(cn, node);
1198 node.parentNode.removeChild(node);
1199 cleanWordChildren();
1203 if (node.className.length) {
1205 var cn = node.className.split(/\W+/);
1207 Roo.each(cn, function(cls) {
1208 if (cls.match(/Mso[a-zA-Z]+/)) {
1213 node.className = cna.length ? cna.join(' ') : '';
1215 node.removeAttribute("class");
1219 if (node.hasAttribute("lang")) {
1220 node.removeAttribute("lang");
1223 if (node.hasAttribute("style")) {
1225 var styles = node.getAttribute("style").split(";");
1227 Roo.each(styles, function(s) {
1228 if (!s.match(/:/)) {
1231 var kv = s.split(":");
1232 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1235 // what ever is left... we allow.
1238 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1239 if (!nstyle.length) {
1240 node.removeAttribute('style');
1244 cleanWordChildren();
1248 domToHTML : function(currentElement, depth, nopadtext) {
1251 nopadtext = nopadtext || false;
1253 if (!currentElement) {
1254 return this.domToHTML(this.doc.body);
1257 //Roo.log(currentElement);
1259 var allText = false;
1260 var nodeName = currentElement.nodeName;
1261 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1263 if (nodeName == '#text') {
1264 return currentElement.nodeValue;
1269 if (nodeName != 'BODY') {
1272 // Prints the node tagName, such as <A>, <IMG>, etc
1275 for(i = 0; i < currentElement.attributes.length;i++) {
1277 var aname = currentElement.attributes.item(i).name;
1278 if (!currentElement.attributes.item(i).value.length) {
1281 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1284 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1293 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1296 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1301 // Traverse the tree
1303 var currentElementChild = currentElement.childNodes.item(i);
1307 while (currentElementChild) {
1308 // Formatting code (indent the tree so it looks nice on the screen)
1309 var nopad = nopadtext;
1310 if (lastnode == 'SPAN') {
1314 if (currentElementChild.nodeName == '#text') {
1315 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1316 if (!nopad && toadd.length > 80) {
1317 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1322 currentElementChild = currentElement.childNodes.item(i);
1328 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1330 // Recursively traverse the tree structure of the child node
1331 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1332 lastnode = currentElementChild.nodeName;
1334 currentElementChild=currentElement.childNodes.item(i);
1340 // The remaining code is mostly for formatting the tree
1341 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1346 ret+= "</"+tagName+">";
1352 // hide stuff that is not compatible
1370 * @cfg {String} fieldClass @hide
1373 * @cfg {String} focusClass @hide
1376 * @cfg {String} autoCreate @hide
1379 * @cfg {String} inputType @hide
1382 * @cfg {String} invalidClass @hide
1385 * @cfg {String} invalidText @hide
1388 * @cfg {String} msgFx @hide
1391 * @cfg {String} validateOnBlur @hide
1395 Roo.HtmlEditorCore.white = [
1396 'area', 'br', 'img', 'input', 'hr', 'wbr',
1398 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1399 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1400 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1401 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1402 'table', 'ul', 'xmp',
1404 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1407 'dir', 'menu', 'ol', 'ul', 'dl',
1413 Roo.HtmlEditorCore.black = [
1414 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1416 'base', 'basefont', 'bgsound', 'blink', 'body',
1417 'frame', 'frameset', 'head', 'html', 'ilayer',
1418 'iframe', 'layer', 'link', 'meta', 'object',
1419 'script', 'style' ,'title', 'xml' // clean later..
1421 Roo.HtmlEditorCore.clean = [
1422 'script', 'style', 'title', 'xml'
1424 Roo.HtmlEditorCore.remove = [
1429 Roo.HtmlEditorCore.ablack = [
1433 Roo.HtmlEditorCore.aclean = [
1434 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1438 Roo.HtmlEditorCore.pwhite= [
1439 'http', 'https', 'mailto'
1442 // white listed style attributes.
1443 Roo.HtmlEditorCore.cwhite= [
1444 // 'text-align', /// default is to allow most things..
1450 // black listed style attributes.
1451 Roo.HtmlEditorCore.cblack= [
1452 // 'font-size' -- this can be set by the project
1456 Roo.HtmlEditorCore.swapCodes =[