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...
80 this.applyBlacklists();
87 Roo.extend(Roo.HtmlEditorCore, Roo.Component, {
91 * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field
97 * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
102 * @cfg {Number} height (in pixels)
106 * @cfg {Number} width (in pixels)
111 * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
119 // private properties
120 validationEvent : false,
124 sourceEditMode : false,
125 onFocus : Roo.emptyFn,
131 // blacklist + whitelisted elements..
138 * Protected method that will not generally be called directly. It
139 * is called when the editor initializes the iframe with HTML contents. Override this method if you
140 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
142 getDocMarkup : function(){
145 Roo.log(this.stylesheets);
147 // inherit styels from page...??
148 if (this.stylesheets === false) {
150 Roo.get(document.head).select('style').each(function(node) {
151 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
154 Roo.get(document.head).select('link').each(function(node) {
155 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
158 } else if (!this.stylesheets.length) {
160 st = '<style type="text/css">' +
161 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
164 Roo.each(this.stylesheets, function(s) {
165 st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
170 st += '<style type="text/css">' +
171 'IMG { cursor: pointer } ' +
175 return '<html><head>' + st +
176 //<style type="text/css">' +
177 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
179 ' </head><body class="roo-htmleditor-body"></body></html>';
183 onRender : function(ct, position)
186 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
187 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
190 this.el.dom.style.border = '0 none';
191 this.el.dom.setAttribute('tabIndex', -1);
192 this.el.addClass('x-hidden hide');
196 if(Roo.isIE){ // fix IE 1px bogus margin
197 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
201 this.frameId = Roo.id();
205 var iframe = this.owner.wrap.createChild({
207 cls: 'form-control', // bootstrap..
211 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
216 this.iframe = iframe.dom;
220 this.doc.designMode = 'on';
223 this.doc.write(this.getDocMarkup());
227 var task = { // must defer to wait for browser to be ready
229 //console.log("run task?" + this.doc.readyState);
231 if(this.doc.body || this.doc.readyState == 'complete'){
233 this.doc.designMode="on";
237 Roo.TaskMgr.stop(task);
238 this.initEditor.defer(10, this);
245 Roo.TaskMgr.start(task);
252 onResize : function(w, h)
254 Roo.log('resize: ' +w + ',' + h );
255 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
259 if(typeof w == 'number'){
261 this.iframe.style.width = w + 'px';
263 if(typeof h == 'number'){
265 this.iframe.style.height = h + 'px';
267 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
274 * Toggles the editor between standard and source edit mode.
275 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
277 toggleSourceEdit : function(sourceEditMode){
279 this.sourceEditMode = sourceEditMode === true;
281 if(this.sourceEditMode){
283 Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
286 Roo.get(this.iframe).removeClass(['x-hidden','hide']);
287 //this.iframe.className = '';
290 //this.setSize(this.owner.wrap.getSize());
291 //this.fireEvent('editmodechange', this, this.sourceEditMode);
298 * Protected method that will not generally be called directly. If you need/want
299 * custom HTML cleanup, this is the method you should override.
300 * @param {String} html The HTML to be cleaned
301 * return {String} The cleaned HTML
303 cleanHtml : function(html){
306 if(Roo.isSafari){ // strip safari nonsense
307 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
310 if(html == ' '){
317 * HTML Editor -> Textarea
318 * Protected method that will not generally be called directly. Syncs the contents
319 * of the editor iframe with the textarea.
321 syncValue : function(){
322 if(this.initialized){
323 var bd = (this.doc.body || this.doc.documentElement);
324 //this.cleanUpPaste(); -- this is done else where and causes havoc..
325 var html = bd.innerHTML;
327 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
328 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
330 html = '<div style="'+m[0]+'">' + html + '</div>';
333 html = this.cleanHtml(html);
334 // fix up the special chars.. normaly like back quotes in word...
335 // however we do not want to do this with chinese..
336 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
337 var cc = b.charCodeAt();
339 (cc >= 0x4E00 && cc < 0xA000 ) ||
340 (cc >= 0x3400 && cc < 0x4E00 ) ||
341 (cc >= 0xf900 && cc < 0xfb00 )
347 if(this.owner.fireEvent('beforesync', this, html) !== false){
348 this.el.dom.value = html;
349 this.owner.fireEvent('sync', this, html);
355 * Protected method that will not generally be called directly. Pushes the value of the textarea
356 * into the iframe editor.
358 pushValue : function(){
359 if(this.initialized){
360 var v = this.el.dom.value.trim();
366 if(this.owner.fireEvent('beforepush', this, v) !== false){
367 var d = (this.doc.body || this.doc.documentElement);
370 this.el.dom.value = d.innerHTML;
371 this.owner.fireEvent('push', this, v);
377 deferFocus : function(){
378 this.focus.defer(10, this);
383 if(this.win && !this.sourceEditMode){
390 assignDocWin: function()
392 var iframe = this.iframe;
395 this.doc = iframe.contentWindow.document;
396 this.win = iframe.contentWindow;
398 // if (!Roo.get(this.frameId)) {
401 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
402 // this.win = Roo.get(this.frameId).dom.contentWindow;
404 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
408 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
409 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
414 initEditor : function(){
415 //console.log("INIT EDITOR");
420 this.doc.designMode="on";
422 this.doc.write(this.getDocMarkup());
425 var dbody = (this.doc.body || this.doc.documentElement);
426 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
427 // this copies styles from the containing element into thsi one..
428 // not sure why we need all of this..
429 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
431 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
432 //ss['background-attachment'] = 'fixed'; // w3c
433 dbody.bgProperties = 'fixed'; // ie
434 //Roo.DomHelper.applyStyles(dbody, ss);
435 Roo.EventManager.on(this.doc, {
436 //'mousedown': this.onEditorEvent,
437 'mouseup': this.onEditorEvent,
438 'dblclick': this.onEditorEvent,
439 'click': this.onEditorEvent,
440 'keyup': this.onEditorEvent,
445 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
447 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
448 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
450 this.initialized = true;
452 this.owner.fireEvent('initialize', this);
457 onDestroy : function(){
463 //for (var i =0; i < this.toolbars.length;i++) {
464 // // fixme - ask toolbars for heights?
465 // this.toolbars[i].onDestroy();
468 //this.wrap.dom.innerHTML = '';
469 //this.wrap.remove();
474 onFirstFocus : function(){
479 this.activated = true;
482 if(Roo.isGecko){ // prevent silly gecko errors
484 var s = this.win.getSelection();
485 if(!s.focusNode || s.focusNode.nodeType != 3){
486 var r = s.getRangeAt(0);
487 r.selectNodeContents((this.doc.body || this.doc.documentElement));
492 this.execCmd('useCSS', true);
493 this.execCmd('styleWithCSS', false);
496 this.owner.fireEvent('activate', this);
500 adjustFont: function(btn){
501 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
502 //if(Roo.isSafari){ // safari
505 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
506 if(Roo.isSafari){ // safari
507 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
508 v = (v < 10) ? 10 : v;
509 v = (v > 48) ? 48 : v;
510 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
515 v = Math.max(1, v+adjust);
517 this.execCmd('FontSize', v );
520 onEditorEvent : function(e){
521 this.owner.fireEvent('editorevent', this, e);
522 // this.updateToolbar();
523 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
526 insertTag : function(tg)
528 // could be a bit smarter... -> wrap the current selected tRoo..
529 if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
531 range = this.createRange(this.getSelection());
532 var wrappingNode = this.doc.createElement(tg.toLowerCase());
533 wrappingNode.appendChild(range.extractContents());
534 range.insertNode(wrappingNode);
541 this.execCmd("formatblock", tg);
545 insertText : function(txt)
549 var range = this.createRange();
550 range.deleteContents();
551 //alert(Sender.getAttribute('label'));
553 range.insertNode(this.doc.createTextNode(txt));
559 * Executes a Midas editor command on the editor document and performs necessary focus and
560 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
561 * @param {String} cmd The Midas command
562 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
564 relayCmd : function(cmd, value){
566 this.execCmd(cmd, value);
567 this.owner.fireEvent('editorevent', this);
568 //this.updateToolbar();
569 this.owner.deferFocus();
573 * Executes a Midas editor command directly on the editor document.
574 * For visual commands, you should use {@link #relayCmd} instead.
575 * <b>This should only be called after the editor is initialized.</b>
576 * @param {String} cmd The Midas command
577 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
579 execCmd : function(cmd, value){
580 this.doc.execCommand(cmd, false, value === undefined ? null : value);
587 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
589 * @param {String} text | dom node..
591 insertAtCursor : function(text)
602 var r = this.doc.selection.createRange();
613 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
617 // from jquery ui (MIT licenced)
621 if (win.getSelection && win.getSelection().getRangeAt) {
622 range = win.getSelection().getRangeAt(0);
623 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
624 range.insertNode(node);
625 } else if (win.document.selection && win.document.selection.createRange) {
626 // no firefox support
627 var txt = typeof(text) == 'string' ? text : text.outerHTML;
628 win.document.selection.createRange().pasteHTML(txt);
630 // no firefox support
631 var txt = typeof(text) == 'string' ? text : text.outerHTML;
632 this.execCmd('InsertHTML', txt);
641 mozKeyPress : function(e){
643 var c = e.getCharCode(), cmd;
646 c = String.fromCharCode(c).toLowerCase();
660 this.cleanUpPaste.defer(100, this);
676 fixKeys : function(){ // load time branching for fastest keydown performance
679 var k = e.getKey(), r;
682 r = this.doc.selection.createRange();
685 r.pasteHTML('    ');
692 r = this.doc.selection.createRange();
694 var target = r.parentElement();
695 if(!target || target.tagName.toLowerCase() != 'li'){
697 r.pasteHTML('<br />');
703 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
704 this.cleanUpPaste.defer(100, this);
710 }else if(Roo.isOpera){
716 this.execCmd('InsertHTML','    ');
719 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
720 this.cleanUpPaste.defer(100, this);
725 }else if(Roo.isSafari){
731 this.execCmd('InsertText','\t');
735 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
736 this.cleanUpPaste.defer(100, this);
744 getAllAncestors: function()
746 var p = this.getSelectedNode();
749 a.push(p); // push blank onto stack..
750 p = this.getParentElement();
754 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
758 a.push(this.doc.body);
765 getSelection : function()
768 return Roo.isIE ? this.doc.selection : this.win.getSelection();
771 getSelectedNode: function()
773 // this may only work on Gecko!!!
775 // should we cache this!!!!
780 var range = this.createRange(this.getSelection()).cloneRange();
783 var parent = range.parentElement();
785 var testRange = range.duplicate();
786 testRange.moveToElementText(parent);
787 if (testRange.inRange(range)) {
790 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
793 parent = parent.parentElement;
798 // is ancestor a text element.
799 var ac = range.commonAncestorContainer;
800 if (ac.nodeType == 3) {
804 var ar = ac.childNodes;
807 var other_nodes = [];
808 var has_other_nodes = false;
809 for (var i=0;i<ar.length;i++) {
810 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
813 // fullly contained node.
815 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
820 // probably selected..
821 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
822 other_nodes.push(ar[i]);
826 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
831 has_other_nodes = true;
833 if (!nodes.length && other_nodes.length) {
836 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
842 createRange: function(sel)
844 // this has strange effects when using with
845 // top toolbar - not sure if it's a great idea.
846 //this.editor.contentWindow.focus();
847 if (typeof sel != "undefined") {
849 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
851 return this.doc.createRange();
854 return this.doc.createRange();
857 getParentElement: function()
861 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
863 var range = this.createRange(sel);
866 var p = range.commonAncestorContainer;
867 while (p.nodeType == 3) { // text node
878 * Range intersection.. the hard stuff...
882 * [ -- selected range --- ]
886 * if end is before start or hits it. fail.
887 * if start is after end or hits it fail.
889 * if either hits (but other is outside. - then it's not
895 // @see http://www.thismuchiknow.co.uk/?p=64.
896 rangeIntersectsNode : function(range, node)
898 var nodeRange = node.ownerDocument.createRange();
900 nodeRange.selectNode(node);
902 nodeRange.selectNodeContents(node);
905 var rangeStartRange = range.cloneRange();
906 rangeStartRange.collapse(true);
908 var rangeEndRange = range.cloneRange();
909 rangeEndRange.collapse(false);
911 var nodeStartRange = nodeRange.cloneRange();
912 nodeStartRange.collapse(true);
914 var nodeEndRange = nodeRange.cloneRange();
915 nodeEndRange.collapse(false);
917 return rangeStartRange.compareBoundaryPoints(
918 Range.START_TO_START, nodeEndRange) == -1 &&
919 rangeEndRange.compareBoundaryPoints(
920 Range.START_TO_START, nodeStartRange) == 1;
924 rangeCompareNode : function(range, node)
926 var nodeRange = node.ownerDocument.createRange();
928 nodeRange.selectNode(node);
930 nodeRange.selectNodeContents(node);
934 range.collapse(true);
936 nodeRange.collapse(true);
938 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
939 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
941 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
943 var nodeIsBefore = ss == 1;
944 var nodeIsAfter = ee == -1;
946 if (nodeIsBefore && nodeIsAfter)
948 if (!nodeIsBefore && nodeIsAfter)
949 return 1; //right trailed.
951 if (nodeIsBefore && !nodeIsAfter)
952 return 2; // left trailed.
957 // private? - in a new class?
958 cleanUpPaste : function()
960 // cleans up the whole document..
961 Roo.log('cleanuppaste');
963 this.cleanUpChildren(this.doc.body);
964 var clean = this.cleanWordChars(this.doc.body.innerHTML);
965 if (clean != this.doc.body.innerHTML) {
966 this.doc.body.innerHTML = clean;
971 cleanWordChars : function(input) {// change the chars to hex code
972 var he = Roo.HtmlEditorCore;
975 Roo.each(he.swapCodes, function(sw) {
976 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
978 output = output.replace(swapper, sw[1]);
985 cleanUpChildren : function (n)
987 if (!n.childNodes.length) {
990 for (var i = n.childNodes.length-1; i > -1 ; i--) {
991 this.cleanUpChild(n.childNodes[i]);
998 cleanUpChild : function (node)
1001 //console.log(node);
1002 if (node.nodeName == "#text") {
1003 // clean up silly Windows -- stuff?
1006 if (node.nodeName == "#comment") {
1007 node.parentNode.removeChild(node);
1008 // clean up silly Windows -- stuff?
1012 if (this.black.indexOf(node.tagName.toLowerCase()) > -1 && this.clearUp) {
1014 node.parentNode.removeChild(node);
1019 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1021 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1022 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1024 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1025 // remove_keep_children = true;
1028 if (remove_keep_children) {
1029 this.cleanUpChildren(node);
1030 // inserts everything just before this node...
1031 while (node.childNodes.length) {
1032 var cn = node.childNodes[0];
1033 node.removeChild(cn);
1034 node.parentNode.insertBefore(cn, node);
1036 node.parentNode.removeChild(node);
1040 if (!node.attributes || !node.attributes.length) {
1041 this.cleanUpChildren(node);
1045 function cleanAttr(n,v)
1048 if (v.match(/^\./) || v.match(/^\//)) {
1051 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1054 if (v.match(/^#/)) {
1057 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1058 node.removeAttribute(n);
1062 function cleanStyle(n,v)
1064 if (v.match(/expression/)) { //XSS?? should we even bother..
1065 node.removeAttribute(n);
1068 var cwhite = typeof(ed.cwhite) != 'undefined' && ed.cwhite !== false ? ed.cwhite : Roo.HtmlEditorCore.cwhite;
1069 var cblack = typeof(ed.cblack) != 'undefined' && ed.cwhite !== false ? ed.cblack : Roo.HtmlEditorCore.cblack;
1072 var parts = v.split(/;/);
1075 Roo.each(parts, function(p) {
1076 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1080 var l = p.split(':').shift().replace(/\s+/g,'');
1081 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1083 if ( cblack.indexOf(l) > -1) {
1084 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1085 //node.removeAttribute(n);
1089 // only allow 'c whitelisted system attributes'
1090 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1091 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1092 //node.removeAttribute(n);
1103 node.setAttribute(n, clean.join(';'));
1105 node.removeAttribute(n);
1111 for (var i = node.attributes.length-1; i > -1 ; i--) {
1112 var a = node.attributes[i];
1115 if (a.name.toLowerCase().substr(0,2)=='on') {
1116 node.removeAttribute(a.name);
1119 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1120 node.removeAttribute(a.name);
1123 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1124 cleanAttr(a.name,a.value); // fixme..
1127 if (a.name == 'style') {
1128 cleanStyle(a.name,a.value);
1131 /// clean up MS crap..
1132 // tecnically this should be a list of valid class'es..
1135 if (a.name == 'class') {
1136 if (a.value.match(/^Mso/)) {
1137 node.className = '';
1140 if (a.value.match(/body/)) {
1141 node.className = '';
1152 this.cleanUpChildren(node);
1157 * Clean up MS wordisms...
1159 cleanWord : function(node)
1162 var cleanWordChildren = function()
1164 if (!node.childNodes.length) {
1167 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1168 _t.cleanWord(node.childNodes[i]);
1174 this.cleanWord(this.doc.body);
1177 if (node.nodeName == "#text") {
1178 // clean up silly Windows -- stuff?
1181 if (node.nodeName == "#comment") {
1182 node.parentNode.removeChild(node);
1183 // clean up silly Windows -- stuff?
1187 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1188 node.parentNode.removeChild(node);
1192 // remove - but keep children..
1193 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1194 while (node.childNodes.length) {
1195 var cn = node.childNodes[0];
1196 node.removeChild(cn);
1197 node.parentNode.insertBefore(cn, node);
1199 node.parentNode.removeChild(node);
1200 cleanWordChildren();
1204 if (node.className.length) {
1206 var cn = node.className.split(/\W+/);
1208 Roo.each(cn, function(cls) {
1209 if (cls.match(/Mso[a-zA-Z]+/)) {
1214 node.className = cna.length ? cna.join(' ') : '';
1216 node.removeAttribute("class");
1220 if (node.hasAttribute("lang")) {
1221 node.removeAttribute("lang");
1224 if (node.hasAttribute("style")) {
1226 var styles = node.getAttribute("style").split(";");
1228 Roo.each(styles, function(s) {
1229 if (!s.match(/:/)) {
1232 var kv = s.split(":");
1233 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1236 // what ever is left... we allow.
1239 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1240 if (!nstyle.length) {
1241 node.removeAttribute('style');
1245 cleanWordChildren();
1249 domToHTML : function(currentElement, depth, nopadtext) {
1252 nopadtext = nopadtext || false;
1254 if (!currentElement) {
1255 return this.domToHTML(this.doc.body);
1258 //Roo.log(currentElement);
1260 var allText = false;
1261 var nodeName = currentElement.nodeName;
1262 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1264 if (nodeName == '#text') {
1265 return currentElement.nodeValue;
1270 if (nodeName != 'BODY') {
1273 // Prints the node tagName, such as <A>, <IMG>, etc
1276 for(i = 0; i < currentElement.attributes.length;i++) {
1278 var aname = currentElement.attributes.item(i).name;
1279 if (!currentElement.attributes.item(i).value.length) {
1282 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1285 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1294 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1297 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1302 // Traverse the tree
1304 var currentElementChild = currentElement.childNodes.item(i);
1308 while (currentElementChild) {
1309 // Formatting code (indent the tree so it looks nice on the screen)
1310 var nopad = nopadtext;
1311 if (lastnode == 'SPAN') {
1315 if (currentElementChild.nodeName == '#text') {
1316 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1317 if (!nopad && toadd.length > 80) {
1318 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1323 currentElementChild = currentElement.childNodes.item(i);
1329 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1331 // Recursively traverse the tree structure of the child node
1332 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1333 lastnode = currentElementChild.nodeName;
1335 currentElementChild=currentElement.childNodes.item(i);
1341 // The remaining code is mostly for formatting the tree
1342 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1347 ret+= "</"+tagName+">";
1353 applyBlacklists : function()
1355 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1356 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1360 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1361 if (b.indexOf(tag) > -1) {
1364 this.white.push(tag);
1368 Roo.each(w, function(tag) {
1369 if (b.indexOf(tag) > -1) {
1372 if (this.white.indexOf(tag) > -1) {
1375 this.white.push(tag);
1380 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1381 if (w.indexOf(tag) > -1) {
1384 this.black.push(tag);
1388 Roo.each(b, function(tag) {
1389 if (w.indexOf(tag) > -1) {
1392 if (this.black.indexOf(tag) > -1) {
1395 this.black.push(tag);
1400 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1401 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1405 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1406 if (b.indexOf(tag) > -1) {
1409 this.cwhite.push(tag);
1413 Roo.each(w, function(tag) {
1414 if (b.indexOf(tag) > -1) {
1417 if (this.cwhite.indexOf(tag) > -1) {
1420 this.cwhite.push(tag);
1425 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1426 if (w.indexOf(tag) > -1) {
1429 this.cblack.push(tag);
1433 Roo.each(b, function(tag) {
1434 if (w.indexOf(tag) > -1) {
1437 if (this.cblack.indexOf(tag) > -1) {
1440 this.cblack.push(tag);
1445 // hide stuff that is not compatible
1463 * @cfg {String} fieldClass @hide
1466 * @cfg {String} focusClass @hide
1469 * @cfg {String} autoCreate @hide
1472 * @cfg {String} inputType @hide
1475 * @cfg {String} invalidClass @hide
1478 * @cfg {String} invalidText @hide
1481 * @cfg {String} msgFx @hide
1484 * @cfg {String} validateOnBlur @hide
1488 Roo.HtmlEditorCore.white = [
1489 'area', 'br', 'img', 'input', 'hr', 'wbr',
1491 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1492 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1493 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1494 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1495 'table', 'ul', 'xmp',
1497 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1500 'dir', 'menu', 'ol', 'ul', 'dl',
1506 Roo.HtmlEditorCore.black = [
1507 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1509 'base', 'basefont', 'bgsound', 'blink', 'body',
1510 'frame', 'frameset', 'head', 'html', 'ilayer',
1511 'iframe', 'layer', 'link', 'meta', 'object',
1512 'script', 'style' ,'title', 'xml' // clean later..
1514 Roo.HtmlEditorCore.clean = [
1515 'script', 'style', 'title', 'xml'
1517 Roo.HtmlEditorCore.remove = [
1522 Roo.HtmlEditorCore.ablack = [
1526 Roo.HtmlEditorCore.aclean = [
1527 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1531 Roo.HtmlEditorCore.pwhite= [
1532 'http', 'https', 'mailto'
1535 // white listed style attributes.
1536 Roo.HtmlEditorCore.cwhite= [
1537 // 'text-align', /// default is to allow most things..
1543 // black listed style attributes.
1544 Roo.HtmlEditorCore.cblack= [
1545 // 'font-size' -- this can be set by the project
1549 Roo.HtmlEditorCore.swapCodes =[