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
78 * Fires when press the Sytlesheets button
79 * @param {Roo.HtmlEditorCore} this
84 // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
86 // defaults : white / black...
87 this.applyBlacklists();
94 Roo.extend(Roo.HtmlEditorCore, Roo.Component, {
98 * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field
104 * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
109 * @cfg {Number} height (in pixels)
113 * @cfg {Number} width (in pixels)
118 * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
126 // private properties
127 validationEvent : false,
131 sourceEditMode : false,
132 onFocus : Roo.emptyFn,
138 // blacklist + whitelisted elements..
145 * Protected method that will not generally be called directly. It
146 * is called when the editor initializes the iframe with HTML contents. Override this method if you
147 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
149 getDocMarkup : function(){
152 Roo.log(this.stylesheets);
154 // inherit styels from page...??
155 if (this.stylesheets === false) {
157 Roo.get(document.head).select('style').each(function(node) {
158 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
161 Roo.get(document.head).select('link').each(function(node) {
162 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
165 } else if (!this.stylesheets.length) {
167 st = '<style type="text/css">' +
168 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
171 Roo.each(this.stylesheets, function(s) {
172 st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
177 st += '<style type="text/css">' +
178 'IMG { cursor: pointer } ' +
182 return '<html><head>' + st +
183 //<style type="text/css">' +
184 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
186 ' </head><body class="roo-htmleditor-body"></body></html>';
190 onRender : function(ct, position)
193 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
194 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
197 this.el.dom.style.border = '0 none';
198 this.el.dom.setAttribute('tabIndex', -1);
199 this.el.addClass('x-hidden hide');
203 if(Roo.isIE){ // fix IE 1px bogus margin
204 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
208 this.frameId = Roo.id();
212 var iframe = this.owner.wrap.createChild({
214 cls: 'form-control', // bootstrap..
218 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
223 this.iframe = iframe.dom;
227 this.doc.designMode = 'on';
230 this.doc.write(this.getDocMarkup());
234 var task = { // must defer to wait for browser to be ready
236 //console.log("run task?" + this.doc.readyState);
238 if(this.doc.body || this.doc.readyState == 'complete'){
240 this.doc.designMode="on";
244 Roo.TaskMgr.stop(task);
245 this.initEditor.defer(10, this);
252 Roo.TaskMgr.start(task);
259 onResize : function(w, h)
261 Roo.log('resize: ' +w + ',' + h );
262 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
266 if(typeof w == 'number'){
268 this.iframe.style.width = w + 'px';
270 if(typeof h == 'number'){
272 this.iframe.style.height = h + 'px';
274 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
281 * Toggles the editor between standard and source edit mode.
282 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
284 toggleSourceEdit : function(sourceEditMode){
286 this.sourceEditMode = sourceEditMode === true;
288 if(this.sourceEditMode){
290 Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
293 Roo.get(this.iframe).removeClass(['x-hidden','hide']);
294 //this.iframe.className = '';
297 //this.setSize(this.owner.wrap.getSize());
298 //this.fireEvent('editmodechange', this, this.sourceEditMode);
305 * Protected method that will not generally be called directly. If you need/want
306 * custom HTML cleanup, this is the method you should override.
307 * @param {String} html The HTML to be cleaned
308 * return {String} The cleaned HTML
310 cleanHtml : function(html){
313 if(Roo.isSafari){ // strip safari nonsense
314 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
317 if(html == ' '){
324 * HTML Editor -> Textarea
325 * Protected method that will not generally be called directly. Syncs the contents
326 * of the editor iframe with the textarea.
328 syncValue : function(){
329 if(this.initialized){
330 var bd = (this.doc.body || this.doc.documentElement);
331 //this.cleanUpPaste(); -- this is done else where and causes havoc..
332 var html = bd.innerHTML;
334 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
335 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
337 html = '<div style="'+m[0]+'">' + html + '</div>';
340 html = this.cleanHtml(html);
341 // fix up the special chars.. normaly like back quotes in word...
342 // however we do not want to do this with chinese..
343 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
344 var cc = b.charCodeAt();
346 (cc >= 0x4E00 && cc < 0xA000 ) ||
347 (cc >= 0x3400 && cc < 0x4E00 ) ||
348 (cc >= 0xf900 && cc < 0xfb00 )
354 if(this.owner.fireEvent('beforesync', this, html) !== false){
355 this.el.dom.value = html;
356 this.owner.fireEvent('sync', this, html);
362 * Protected method that will not generally be called directly. Pushes the value of the textarea
363 * into the iframe editor.
365 pushValue : function(){
366 if(this.initialized){
367 var v = this.el.dom.value.trim();
373 if(this.owner.fireEvent('beforepush', this, v) !== false){
374 var d = (this.doc.body || this.doc.documentElement);
377 this.el.dom.value = d.innerHTML;
378 this.owner.fireEvent('push', this, v);
384 deferFocus : function(){
385 this.focus.defer(10, this);
390 if(this.win && !this.sourceEditMode){
397 assignDocWin: function()
399 var iframe = this.iframe;
402 this.doc = iframe.contentWindow.document;
403 this.win = iframe.contentWindow;
405 // if (!Roo.get(this.frameId)) {
408 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
409 // this.win = Roo.get(this.frameId).dom.contentWindow;
411 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
415 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
416 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
421 initEditor : function(){
422 //console.log("INIT EDITOR");
427 this.doc.designMode="on";
429 this.doc.write(this.getDocMarkup());
432 var dbody = (this.doc.body || this.doc.documentElement);
433 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
434 // this copies styles from the containing element into thsi one..
435 // not sure why we need all of this..
436 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
438 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
439 //ss['background-attachment'] = 'fixed'; // w3c
440 dbody.bgProperties = 'fixed'; // ie
441 //Roo.DomHelper.applyStyles(dbody, ss);
442 Roo.EventManager.on(this.doc, {
443 //'mousedown': this.onEditorEvent,
444 'mouseup': this.onEditorEvent,
445 'dblclick': this.onEditorEvent,
446 'click': this.onEditorEvent,
447 'keyup': this.onEditorEvent,
452 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
454 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
455 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
457 this.initialized = true;
459 this.owner.fireEvent('initialize', this);
464 onDestroy : function(){
470 //for (var i =0; i < this.toolbars.length;i++) {
471 // // fixme - ask toolbars for heights?
472 // this.toolbars[i].onDestroy();
475 //this.wrap.dom.innerHTML = '';
476 //this.wrap.remove();
481 onFirstFocus : function(){
486 this.activated = true;
489 if(Roo.isGecko){ // prevent silly gecko errors
491 var s = this.win.getSelection();
492 if(!s.focusNode || s.focusNode.nodeType != 3){
493 var r = s.getRangeAt(0);
494 r.selectNodeContents((this.doc.body || this.doc.documentElement));
499 this.execCmd('useCSS', true);
500 this.execCmd('styleWithCSS', false);
503 this.owner.fireEvent('activate', this);
507 adjustFont: function(btn){
508 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
509 //if(Roo.isSafari){ // safari
512 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
513 if(Roo.isSafari){ // safari
514 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
515 v = (v < 10) ? 10 : v;
516 v = (v > 48) ? 48 : v;
517 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
522 v = Math.max(1, v+adjust);
524 this.execCmd('FontSize', v );
527 onEditorEvent : function(e){
528 this.owner.fireEvent('editorevent', this, e);
529 // this.updateToolbar();
530 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
533 insertTag : function(tg)
535 // could be a bit smarter... -> wrap the current selected tRoo..
536 if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
538 range = this.createRange(this.getSelection());
539 var wrappingNode = this.doc.createElement(tg.toLowerCase());
540 wrappingNode.appendChild(range.extractContents());
541 range.insertNode(wrappingNode);
548 this.execCmd("formatblock", tg);
552 insertText : function(txt)
556 var range = this.createRange();
557 range.deleteContents();
558 //alert(Sender.getAttribute('label'));
560 range.insertNode(this.doc.createTextNode(txt));
566 * Executes a Midas editor command on the editor document and performs necessary focus and
567 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
568 * @param {String} cmd The Midas command
569 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
571 relayCmd : function(cmd, value){
573 this.execCmd(cmd, value);
574 this.owner.fireEvent('editorevent', this);
575 //this.updateToolbar();
576 this.owner.deferFocus();
580 * Executes a Midas editor command directly on the editor document.
581 * For visual commands, you should use {@link #relayCmd} instead.
582 * <b>This should only be called after the editor is initialized.</b>
583 * @param {String} cmd The Midas command
584 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
586 execCmd : function(cmd, value){
587 this.doc.execCommand(cmd, false, value === undefined ? null : value);
594 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
596 * @param {String} text | dom node..
598 insertAtCursor : function(text)
609 var r = this.doc.selection.createRange();
620 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
624 // from jquery ui (MIT licenced)
628 if (win.getSelection && win.getSelection().getRangeAt) {
629 range = win.getSelection().getRangeAt(0);
630 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
631 range.insertNode(node);
632 } else if (win.document.selection && win.document.selection.createRange) {
633 // no firefox support
634 var txt = typeof(text) == 'string' ? text : text.outerHTML;
635 win.document.selection.createRange().pasteHTML(txt);
637 // no firefox support
638 var txt = typeof(text) == 'string' ? text : text.outerHTML;
639 this.execCmd('InsertHTML', txt);
648 mozKeyPress : function(e){
650 var c = e.getCharCode(), cmd;
653 c = String.fromCharCode(c).toLowerCase();
667 this.cleanUpPaste.defer(100, this);
683 fixKeys : function(){ // load time branching for fastest keydown performance
686 var k = e.getKey(), r;
689 r = this.doc.selection.createRange();
692 r.pasteHTML('    ');
699 r = this.doc.selection.createRange();
701 var target = r.parentElement();
702 if(!target || target.tagName.toLowerCase() != 'li'){
704 r.pasteHTML('<br />');
710 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
711 this.cleanUpPaste.defer(100, this);
717 }else if(Roo.isOpera){
723 this.execCmd('InsertHTML','    ');
726 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
727 this.cleanUpPaste.defer(100, this);
732 }else if(Roo.isSafari){
738 this.execCmd('InsertText','\t');
742 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
743 this.cleanUpPaste.defer(100, this);
751 getAllAncestors: function()
753 var p = this.getSelectedNode();
756 a.push(p); // push blank onto stack..
757 p = this.getParentElement();
761 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
765 a.push(this.doc.body);
772 getSelection : function()
775 return Roo.isIE ? this.doc.selection : this.win.getSelection();
778 getSelectedNode: function()
780 // this may only work on Gecko!!!
782 // should we cache this!!!!
787 var range = this.createRange(this.getSelection()).cloneRange();
790 var parent = range.parentElement();
792 var testRange = range.duplicate();
793 testRange.moveToElementText(parent);
794 if (testRange.inRange(range)) {
797 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
800 parent = parent.parentElement;
805 // is ancestor a text element.
806 var ac = range.commonAncestorContainer;
807 if (ac.nodeType == 3) {
811 var ar = ac.childNodes;
814 var other_nodes = [];
815 var has_other_nodes = false;
816 for (var i=0;i<ar.length;i++) {
817 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
820 // fullly contained node.
822 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
827 // probably selected..
828 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
829 other_nodes.push(ar[i]);
833 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
838 has_other_nodes = true;
840 if (!nodes.length && other_nodes.length) {
843 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
849 createRange: function(sel)
851 // this has strange effects when using with
852 // top toolbar - not sure if it's a great idea.
853 //this.editor.contentWindow.focus();
854 if (typeof sel != "undefined") {
856 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
858 return this.doc.createRange();
861 return this.doc.createRange();
864 getParentElement: function()
868 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
870 var range = this.createRange(sel);
873 var p = range.commonAncestorContainer;
874 while (p.nodeType == 3) { // text node
885 * Range intersection.. the hard stuff...
889 * [ -- selected range --- ]
893 * if end is before start or hits it. fail.
894 * if start is after end or hits it fail.
896 * if either hits (but other is outside. - then it's not
902 // @see http://www.thismuchiknow.co.uk/?p=64.
903 rangeIntersectsNode : function(range, node)
905 var nodeRange = node.ownerDocument.createRange();
907 nodeRange.selectNode(node);
909 nodeRange.selectNodeContents(node);
912 var rangeStartRange = range.cloneRange();
913 rangeStartRange.collapse(true);
915 var rangeEndRange = range.cloneRange();
916 rangeEndRange.collapse(false);
918 var nodeStartRange = nodeRange.cloneRange();
919 nodeStartRange.collapse(true);
921 var nodeEndRange = nodeRange.cloneRange();
922 nodeEndRange.collapse(false);
924 return rangeStartRange.compareBoundaryPoints(
925 Range.START_TO_START, nodeEndRange) == -1 &&
926 rangeEndRange.compareBoundaryPoints(
927 Range.START_TO_START, nodeStartRange) == 1;
931 rangeCompareNode : function(range, node)
933 var nodeRange = node.ownerDocument.createRange();
935 nodeRange.selectNode(node);
937 nodeRange.selectNodeContents(node);
941 range.collapse(true);
943 nodeRange.collapse(true);
945 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
946 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
948 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
950 var nodeIsBefore = ss == 1;
951 var nodeIsAfter = ee == -1;
953 if (nodeIsBefore && nodeIsAfter)
955 if (!nodeIsBefore && nodeIsAfter)
956 return 1; //right trailed.
958 if (nodeIsBefore && !nodeIsAfter)
959 return 2; // left trailed.
964 // private? - in a new class?
965 cleanUpPaste : function()
967 // cleans up the whole document..
968 Roo.log('cleanuppaste');
970 this.cleanUpChildren(this.doc.body);
971 var clean = this.cleanWordChars(this.doc.body.innerHTML);
972 if (clean != this.doc.body.innerHTML) {
973 this.doc.body.innerHTML = clean;
978 cleanWordChars : function(input) {// change the chars to hex code
979 var he = Roo.HtmlEditorCore;
982 Roo.each(he.swapCodes, function(sw) {
983 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
985 output = output.replace(swapper, sw[1]);
992 cleanUpChildren : function (n)
994 if (!n.childNodes.length) {
997 for (var i = n.childNodes.length-1; i > -1 ; i--) {
998 this.cleanUpChild(n.childNodes[i]);
1005 cleanUpChild : function (node)
1008 //console.log(node);
1009 if (node.nodeName == "#text") {
1010 // clean up silly Windows -- stuff?
1013 if (node.nodeName == "#comment") {
1014 node.parentNode.removeChild(node);
1015 // clean up silly Windows -- stuff?
1018 var lcname = node.tagName.toLowerCase();
1019 // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full
1020 // whitelist of tags..
1022 if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
1024 node.parentNode.removeChild(node);
1029 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1031 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1032 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1034 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1035 // remove_keep_children = true;
1038 if (remove_keep_children) {
1039 this.cleanUpChildren(node);
1040 // inserts everything just before this node...
1041 while (node.childNodes.length) {
1042 var cn = node.childNodes[0];
1043 node.removeChild(cn);
1044 node.parentNode.insertBefore(cn, node);
1046 node.parentNode.removeChild(node);
1050 if (!node.attributes || !node.attributes.length) {
1051 this.cleanUpChildren(node);
1055 function cleanAttr(n,v)
1058 if (v.match(/^\./) || v.match(/^\//)) {
1061 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1064 if (v.match(/^#/)) {
1067 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1068 node.removeAttribute(n);
1072 var cwhite = this.cwhite;
1073 var cblack = this.cblack;
1075 function cleanStyle(n,v)
1077 if (v.match(/expression/)) { //XSS?? should we even bother..
1078 node.removeAttribute(n);
1082 var parts = v.split(/;/);
1085 Roo.each(parts, function(p) {
1086 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1090 var l = p.split(':').shift().replace(/\s+/g,'');
1091 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1093 if ( cwhite.length && cblack.indexOf(l) > -1) {
1094 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1095 //node.removeAttribute(n);
1099 // only allow 'c whitelisted system attributes'
1100 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1101 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1102 //node.removeAttribute(n);
1113 node.setAttribute(n, clean.join(';'));
1115 node.removeAttribute(n);
1121 for (var i = node.attributes.length-1; i > -1 ; i--) {
1122 var a = node.attributes[i];
1125 if (a.name.toLowerCase().substr(0,2)=='on') {
1126 node.removeAttribute(a.name);
1129 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1130 node.removeAttribute(a.name);
1133 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1134 cleanAttr(a.name,a.value); // fixme..
1137 if (a.name == 'style') {
1138 cleanStyle(a.name,a.value);
1141 /// clean up MS crap..
1142 // tecnically this should be a list of valid class'es..
1145 if (a.name == 'class') {
1146 if (a.value.match(/^Mso/)) {
1147 node.className = '';
1150 if (a.value.match(/body/)) {
1151 node.className = '';
1162 this.cleanUpChildren(node);
1167 * Clean up MS wordisms...
1169 cleanWord : function(node)
1172 var cleanWordChildren = function()
1174 if (!node.childNodes.length) {
1177 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1178 _t.cleanWord(node.childNodes[i]);
1184 this.cleanWord(this.doc.body);
1187 if (node.nodeName == "#text") {
1188 // clean up silly Windows -- stuff?
1191 if (node.nodeName == "#comment") {
1192 node.parentNode.removeChild(node);
1193 // clean up silly Windows -- stuff?
1197 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1198 node.parentNode.removeChild(node);
1202 // remove - but keep children..
1203 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1204 while (node.childNodes.length) {
1205 var cn = node.childNodes[0];
1206 node.removeChild(cn);
1207 node.parentNode.insertBefore(cn, node);
1209 node.parentNode.removeChild(node);
1210 cleanWordChildren();
1214 if (node.className.length) {
1216 var cn = node.className.split(/\W+/);
1218 Roo.each(cn, function(cls) {
1219 if (cls.match(/Mso[a-zA-Z]+/)) {
1224 node.className = cna.length ? cna.join(' ') : '';
1226 node.removeAttribute("class");
1230 if (node.hasAttribute("lang")) {
1231 node.removeAttribute("lang");
1234 if (node.hasAttribute("style")) {
1236 var styles = node.getAttribute("style").split(";");
1238 Roo.each(styles, function(s) {
1239 if (!s.match(/:/)) {
1242 var kv = s.split(":");
1243 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1246 // what ever is left... we allow.
1249 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1250 if (!nstyle.length) {
1251 node.removeAttribute('style');
1255 cleanWordChildren();
1259 domToHTML : function(currentElement, depth, nopadtext) {
1262 nopadtext = nopadtext || false;
1264 if (!currentElement) {
1265 return this.domToHTML(this.doc.body);
1268 //Roo.log(currentElement);
1270 var allText = false;
1271 var nodeName = currentElement.nodeName;
1272 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1274 if (nodeName == '#text') {
1276 return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim();
1281 if (nodeName != 'BODY') {
1284 // Prints the node tagName, such as <A>, <IMG>, etc
1287 for(i = 0; i < currentElement.attributes.length;i++) {
1289 var aname = currentElement.attributes.item(i).name;
1290 if (!currentElement.attributes.item(i).value.length) {
1293 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1296 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1305 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1308 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1313 // Traverse the tree
1315 var currentElementChild = currentElement.childNodes.item(i);
1319 while (currentElementChild) {
1320 // Formatting code (indent the tree so it looks nice on the screen)
1321 var nopad = nopadtext;
1322 if (lastnode == 'SPAN') {
1326 if (currentElementChild.nodeName == '#text') {
1327 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1328 toadd = nopadtext ? toadd : toadd.trim();
1329 if (!nopad && toadd.length > 80) {
1330 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1335 currentElementChild = currentElement.childNodes.item(i);
1341 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1343 // Recursively traverse the tree structure of the child node
1344 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1345 lastnode = currentElementChild.nodeName;
1347 currentElementChild=currentElement.childNodes.item(i);
1353 // The remaining code is mostly for formatting the tree
1354 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1359 ret+= "</"+tagName+">";
1365 applyBlacklists : function()
1367 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1368 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1372 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1373 if (b.indexOf(tag) > -1) {
1376 this.white.push(tag);
1380 Roo.each(w, function(tag) {
1381 if (b.indexOf(tag) > -1) {
1384 if (this.white.indexOf(tag) > -1) {
1387 this.white.push(tag);
1392 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1393 if (w.indexOf(tag) > -1) {
1396 this.black.push(tag);
1400 Roo.each(b, function(tag) {
1401 if (w.indexOf(tag) > -1) {
1404 if (this.black.indexOf(tag) > -1) {
1407 this.black.push(tag);
1412 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1413 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1417 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1418 if (b.indexOf(tag) > -1) {
1421 this.cwhite.push(tag);
1425 Roo.each(w, function(tag) {
1426 if (b.indexOf(tag) > -1) {
1429 if (this.cwhite.indexOf(tag) > -1) {
1432 this.cwhite.push(tag);
1437 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1438 if (w.indexOf(tag) > -1) {
1441 this.cblack.push(tag);
1445 Roo.each(b, function(tag) {
1446 if (w.indexOf(tag) > -1) {
1449 if (this.cblack.indexOf(tag) > -1) {
1452 this.cblack.push(tag);
1457 // hide stuff that is not compatible
1475 * @cfg {String} fieldClass @hide
1478 * @cfg {String} focusClass @hide
1481 * @cfg {String} autoCreate @hide
1484 * @cfg {String} inputType @hide
1487 * @cfg {String} invalidClass @hide
1490 * @cfg {String} invalidText @hide
1493 * @cfg {String} msgFx @hide
1496 * @cfg {String} validateOnBlur @hide
1500 Roo.HtmlEditorCore.white = [
1501 'area', 'br', 'img', 'input', 'hr', 'wbr',
1503 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1504 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1505 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1506 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1507 'table', 'ul', 'xmp',
1509 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1512 'dir', 'menu', 'ol', 'ul', 'dl',
1518 Roo.HtmlEditorCore.black = [
1519 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1521 'base', 'basefont', 'bgsound', 'blink', 'body',
1522 'frame', 'frameset', 'head', 'html', 'ilayer',
1523 'iframe', 'layer', 'link', 'meta', 'object',
1524 'script', 'style' ,'title', 'xml' // clean later..
1526 Roo.HtmlEditorCore.clean = [
1527 'script', 'style', 'title', 'xml'
1529 Roo.HtmlEditorCore.remove = [
1534 Roo.HtmlEditorCore.ablack = [
1538 Roo.HtmlEditorCore.aclean = [
1539 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1543 Roo.HtmlEditorCore.pwhite= [
1544 'http', 'https', 'mailto'
1547 // white listed style attributes.
1548 Roo.HtmlEditorCore.cwhite= [
1549 // 'text-align', /// default is to allow most things..
1555 // black listed style attributes.
1556 Roo.HtmlEditorCore.cblack= [
1557 // 'font-size' -- this can be set by the project
1561 Roo.HtmlEditorCore.swapCodes =[