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 // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
80 // defaults : white / black...
81 this.applyBlacklists();
88 Roo.extend(Roo.HtmlEditorCore, Roo.Component, {
92 * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field
98 * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
103 * @cfg {Number} height (in pixels)
107 * @cfg {Number} width (in pixels)
112 * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
120 // private properties
121 validationEvent : false,
125 sourceEditMode : false,
126 onFocus : Roo.emptyFn,
132 // blacklist + whitelisted elements..
139 * Protected method that will not generally be called directly. It
140 * is called when the editor initializes the iframe with HTML contents. Override this method if you
141 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
143 getDocMarkup : function(){
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 st = '<style type="text/css">' +
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);
249 onResize : function(w, h)
251 Roo.log('resize: ' +w + ',' + h );
252 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
256 if(typeof w == 'number'){
258 this.iframe.style.width = w + 'px';
260 if(typeof h == 'number'){
262 this.iframe.style.height = h + 'px';
264 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
271 * Toggles the editor between standard and source edit mode.
272 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
274 toggleSourceEdit : function(sourceEditMode){
276 this.sourceEditMode = sourceEditMode === true;
278 if(this.sourceEditMode){
280 Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
283 Roo.get(this.iframe).removeClass(['x-hidden','hide']);
284 //this.iframe.className = '';
287 //this.setSize(this.owner.wrap.getSize());
288 //this.fireEvent('editmodechange', this, this.sourceEditMode);
295 * Protected method that will not generally be called directly. If you need/want
296 * custom HTML cleanup, this is the method you should override.
297 * @param {String} html The HTML to be cleaned
298 * return {String} The cleaned HTML
300 cleanHtml : function(html){
303 if(Roo.isSafari){ // strip safari nonsense
304 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
307 if(html == ' '){
314 * HTML Editor -> Textarea
315 * Protected method that will not generally be called directly. Syncs the contents
316 * of the editor iframe with the textarea.
318 syncValue : function(){
319 if(this.initialized){
320 var bd = (this.doc.body || this.doc.documentElement);
321 //this.cleanUpPaste(); -- this is done else where and causes havoc..
322 var html = bd.innerHTML;
324 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
325 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
327 html = '<div style="'+m[0]+'">' + html + '</div>';
330 html = this.cleanHtml(html);
331 // fix up the special chars.. normaly like back quotes in word...
332 // however we do not want to do this with chinese..
333 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
334 var cc = b.charCodeAt();
336 (cc >= 0x4E00 && cc < 0xA000 ) ||
337 (cc >= 0x3400 && cc < 0x4E00 ) ||
338 (cc >= 0xf900 && cc < 0xfb00 )
344 if(this.owner.fireEvent('beforesync', this, html) !== false){
345 this.el.dom.value = html;
346 this.owner.fireEvent('sync', this, html);
352 * Protected method that will not generally be called directly. Pushes the value of the textarea
353 * into the iframe editor.
355 pushValue : function(){
356 if(this.initialized){
357 var v = this.el.dom.value.trim();
363 if(this.owner.fireEvent('beforepush', this, v) !== false){
364 var d = (this.doc.body || this.doc.documentElement);
367 this.el.dom.value = d.innerHTML;
368 this.owner.fireEvent('push', this, v);
374 deferFocus : function(){
375 this.focus.defer(10, this);
380 if(this.win && !this.sourceEditMode){
387 assignDocWin: function()
389 var iframe = this.iframe;
392 this.doc = iframe.contentWindow.document;
393 this.win = iframe.contentWindow;
395 // if (!Roo.get(this.frameId)) {
398 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
399 // this.win = Roo.get(this.frameId).dom.contentWindow;
401 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
405 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
406 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
411 initEditor : function(){
412 //console.log("INIT EDITOR");
417 this.doc.designMode="on";
419 this.doc.write(this.getDocMarkup());
422 var dbody = (this.doc.body || this.doc.documentElement);
423 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
424 // this copies styles from the containing element into thsi one..
425 // not sure why we need all of this..
426 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
428 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
429 //ss['background-attachment'] = 'fixed'; // w3c
430 dbody.bgProperties = 'fixed'; // ie
431 //Roo.DomHelper.applyStyles(dbody, ss);
432 Roo.EventManager.on(this.doc, {
433 //'mousedown': this.onEditorEvent,
434 'mouseup': this.onEditorEvent,
435 'dblclick': this.onEditorEvent,
436 'click': this.onEditorEvent,
437 'keyup': this.onEditorEvent,
442 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
444 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
445 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
447 this.initialized = true;
449 this.owner.fireEvent('initialize', this);
454 onDestroy : function(){
460 //for (var i =0; i < this.toolbars.length;i++) {
461 // // fixme - ask toolbars for heights?
462 // this.toolbars[i].onDestroy();
465 //this.wrap.dom.innerHTML = '';
466 //this.wrap.remove();
471 onFirstFocus : function(){
476 this.activated = true;
479 if(Roo.isGecko){ // prevent silly gecko errors
481 var s = this.win.getSelection();
482 if(!s.focusNode || s.focusNode.nodeType != 3){
483 var r = s.getRangeAt(0);
484 r.selectNodeContents((this.doc.body || this.doc.documentElement));
489 this.execCmd('useCSS', true);
490 this.execCmd('styleWithCSS', false);
493 this.owner.fireEvent('activate', this);
497 adjustFont: function(btn){
498 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
499 //if(Roo.isSafari){ // safari
502 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
503 if(Roo.isSafari){ // safari
504 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
505 v = (v < 10) ? 10 : v;
506 v = (v > 48) ? 48 : v;
507 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
512 v = Math.max(1, v+adjust);
514 this.execCmd('FontSize', v );
517 onEditorEvent : function(e)
519 this.owner.fireEvent('editorevent', this, e);
520 // this.updateToolbar();
521 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
524 insertTag : function(tg)
526 // could be a bit smarter... -> wrap the current selected tRoo..
527 if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
529 range = this.createRange(this.getSelection());
530 var wrappingNode = this.doc.createElement(tg.toLowerCase());
531 wrappingNode.appendChild(range.extractContents());
532 range.insertNode(wrappingNode);
539 this.execCmd("formatblock", tg);
543 insertText : function(txt)
547 var range = this.createRange();
548 range.deleteContents();
549 //alert(Sender.getAttribute('label'));
551 range.insertNode(this.doc.createTextNode(txt));
557 * Executes a Midas editor command on the editor document and performs necessary focus and
558 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
559 * @param {String} cmd The Midas command
560 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
562 relayCmd : function(cmd, value){
564 this.execCmd(cmd, value);
565 this.owner.fireEvent('editorevent', this);
566 //this.updateToolbar();
567 this.owner.deferFocus();
571 * Executes a Midas editor command directly on the editor document.
572 * For visual commands, you should use {@link #relayCmd} instead.
573 * <b>This should only be called after the editor is initialized.</b>
574 * @param {String} cmd The Midas command
575 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
577 execCmd : function(cmd, value){
578 this.doc.execCommand(cmd, false, value === undefined ? null : value);
585 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
587 * @param {String} text | dom node..
589 insertAtCursor : function(text)
598 var r = this.doc.selection.createRange();
609 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
613 // from jquery ui (MIT licenced)
617 if (win.getSelection && win.getSelection().getRangeAt) {
618 range = win.getSelection().getRangeAt(0);
619 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
620 range.insertNode(node);
621 } else if (win.document.selection && win.document.selection.createRange) {
622 // no firefox support
623 var txt = typeof(text) == 'string' ? text : text.outerHTML;
624 win.document.selection.createRange().pasteHTML(txt);
626 // no firefox support
627 var txt = typeof(text) == 'string' ? text : text.outerHTML;
628 this.execCmd('InsertHTML', txt);
637 mozKeyPress : function(e){
639 var c = e.getCharCode(), cmd;
642 c = String.fromCharCode(c).toLowerCase();
656 this.cleanUpPaste.defer(100, this);
672 fixKeys : function(){ // load time branching for fastest keydown performance
675 var k = e.getKey(), r;
678 r = this.doc.selection.createRange();
681 r.pasteHTML('    ');
688 r = this.doc.selection.createRange();
690 var target = r.parentElement();
691 if(!target || target.tagName.toLowerCase() != 'li'){
693 r.pasteHTML('<br />');
699 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
700 this.cleanUpPaste.defer(100, this);
706 }else if(Roo.isOpera){
712 this.execCmd('InsertHTML','    ');
715 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
716 this.cleanUpPaste.defer(100, this);
721 }else if(Roo.isSafari){
727 this.execCmd('InsertText','\t');
731 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
732 this.cleanUpPaste.defer(100, this);
740 getAllAncestors: function()
742 var p = this.getSelectedNode();
745 a.push(p); // push blank onto stack..
746 p = this.getParentElement();
750 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
754 a.push(this.doc.body);
761 getSelection : function()
764 return Roo.isIE ? this.doc.selection : this.win.getSelection();
767 getSelectedNode: function()
769 // this may only work on Gecko!!!
771 // should we cache this!!!!
776 var range = this.createRange(this.getSelection()).cloneRange();
779 var parent = range.parentElement();
781 var testRange = range.duplicate();
782 testRange.moveToElementText(parent);
783 if (testRange.inRange(range)) {
786 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
789 parent = parent.parentElement;
794 // is ancestor a text element.
795 var ac = range.commonAncestorContainer;
796 if (ac.nodeType == 3) {
800 var ar = ac.childNodes;
803 var other_nodes = [];
804 var has_other_nodes = false;
805 for (var i=0;i<ar.length;i++) {
806 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
809 // fullly contained node.
811 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
816 // probably selected..
817 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
818 other_nodes.push(ar[i]);
822 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
827 has_other_nodes = true;
829 if (!nodes.length && other_nodes.length) {
832 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
838 createRange: function(sel)
840 // this has strange effects when using with
841 // top toolbar - not sure if it's a great idea.
842 //this.editor.contentWindow.focus();
843 if (typeof sel != "undefined") {
845 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
847 return this.doc.createRange();
850 return this.doc.createRange();
853 getParentElement: function()
857 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
859 var range = this.createRange(sel);
862 var p = range.commonAncestorContainer;
863 while (p.nodeType == 3) { // text node
874 * Range intersection.. the hard stuff...
878 * [ -- selected range --- ]
882 * if end is before start or hits it. fail.
883 * if start is after end or hits it fail.
885 * if either hits (but other is outside. - then it's not
891 // @see http://www.thismuchiknow.co.uk/?p=64.
892 rangeIntersectsNode : function(range, node)
894 var nodeRange = node.ownerDocument.createRange();
896 nodeRange.selectNode(node);
898 nodeRange.selectNodeContents(node);
901 var rangeStartRange = range.cloneRange();
902 rangeStartRange.collapse(true);
904 var rangeEndRange = range.cloneRange();
905 rangeEndRange.collapse(false);
907 var nodeStartRange = nodeRange.cloneRange();
908 nodeStartRange.collapse(true);
910 var nodeEndRange = nodeRange.cloneRange();
911 nodeEndRange.collapse(false);
913 return rangeStartRange.compareBoundaryPoints(
914 Range.START_TO_START, nodeEndRange) == -1 &&
915 rangeEndRange.compareBoundaryPoints(
916 Range.START_TO_START, nodeStartRange) == 1;
920 rangeCompareNode : function(range, node)
922 var nodeRange = node.ownerDocument.createRange();
924 nodeRange.selectNode(node);
926 nodeRange.selectNodeContents(node);
930 range.collapse(true);
932 nodeRange.collapse(true);
934 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
935 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
937 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
939 var nodeIsBefore = ss == 1;
940 var nodeIsAfter = ee == -1;
942 if (nodeIsBefore && nodeIsAfter) {
945 if (!nodeIsBefore && nodeIsAfter) {
946 return 1; //right trailed.
949 if (nodeIsBefore && !nodeIsAfter) {
950 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?
1010 var lcname = node.tagName.toLowerCase();
1011 // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full
1012 // whitelist of tags..
1014 if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
1016 node.parentNode.removeChild(node);
1021 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1023 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1024 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1026 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1027 // remove_keep_children = true;
1030 if (remove_keep_children) {
1031 this.cleanUpChildren(node);
1032 // inserts everything just before this node...
1033 while (node.childNodes.length) {
1034 var cn = node.childNodes[0];
1035 node.removeChild(cn);
1036 node.parentNode.insertBefore(cn, node);
1038 node.parentNode.removeChild(node);
1042 if (!node.attributes || !node.attributes.length) {
1043 this.cleanUpChildren(node);
1047 function cleanAttr(n,v)
1050 if (v.match(/^\./) || v.match(/^\//)) {
1053 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1056 if (v.match(/^#/)) {
1059 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1060 node.removeAttribute(n);
1064 var cwhite = this.cwhite;
1065 var cblack = this.cblack;
1067 function cleanStyle(n,v)
1069 if (v.match(/expression/)) { //XSS?? should we even bother..
1070 node.removeAttribute(n);
1074 var parts = v.split(/;/);
1077 Roo.each(parts, function(p) {
1078 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1082 var l = p.split(':').shift().replace(/\s+/g,'');
1083 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1085 if ( cwhite.length && cblack.indexOf(l) > -1) {
1086 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1087 //node.removeAttribute(n);
1091 // only allow 'c whitelisted system attributes'
1092 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1093 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1094 //node.removeAttribute(n);
1105 node.setAttribute(n, clean.join(';'));
1107 node.removeAttribute(n);
1113 for (var i = node.attributes.length-1; i > -1 ; i--) {
1114 var a = node.attributes[i];
1117 if (a.name.toLowerCase().substr(0,2)=='on') {
1118 node.removeAttribute(a.name);
1121 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1122 node.removeAttribute(a.name);
1125 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1126 cleanAttr(a.name,a.value); // fixme..
1129 if (a.name == 'style') {
1130 cleanStyle(a.name,a.value);
1133 /// clean up MS crap..
1134 // tecnically this should be a list of valid class'es..
1137 if (a.name == 'class') {
1138 if (a.value.match(/^Mso/)) {
1139 node.className = '';
1142 if (a.value.match(/^body$/)) {
1143 node.className = '';
1154 this.cleanUpChildren(node);
1160 * Clean up MS wordisms...
1162 cleanWord : function(node)
1167 this.cleanWord(this.doc.body);
1170 if (node.nodeName == "#text") {
1171 // clean up silly Windows -- stuff?
1174 if (node.nodeName == "#comment") {
1175 node.parentNode.removeChild(node);
1176 // clean up silly Windows -- stuff?
1180 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1181 node.parentNode.removeChild(node);
1185 // remove - but keep children..
1186 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1187 while (node.childNodes.length) {
1188 var cn = node.childNodes[0];
1189 node.removeChild(cn);
1190 node.parentNode.insertBefore(cn, node);
1192 node.parentNode.removeChild(node);
1193 this.iterateChildren(node, this.cleanWord);
1197 if (node.className.length) {
1199 var cn = node.className.split(/\W+/);
1201 Roo.each(cn, function(cls) {
1202 if (cls.match(/Mso[a-zA-Z]+/)) {
1207 node.className = cna.length ? cna.join(' ') : '';
1209 node.removeAttribute("class");
1213 if (node.hasAttribute("lang")) {
1214 node.removeAttribute("lang");
1217 if (node.hasAttribute("style")) {
1219 var styles = node.getAttribute("style").split(";");
1221 Roo.each(styles, function(s) {
1222 if (!s.match(/:/)) {
1225 var kv = s.split(":");
1226 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1229 // what ever is left... we allow.
1232 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1233 if (!nstyle.length) {
1234 node.removeAttribute('style');
1237 this.iterateChildren(node, this.cleanWord);
1243 * iterateChildren of a Node, calling fn each time, using this as the scole..
1244 * @param {DomNode} node node to iterate children of.
1245 * @param {Function} fn method of this class to call on each item.
1247 iterateChildren : function(node, fn)
1249 if (!node.childNodes.length) {
1252 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1253 fn.call(this, node.childNodes[i])
1261 * Quite often pasting from word etc.. results in tables with column and widths.
1262 * This does not work well on fluid HTML layouts - like emails. - so this code should hunt an destroy them..
1265 cleanTableWidths : function(node)
1270 this.cleanTableWidths(this.doc.body);
1275 if (node.nodeName == "#text" || node.nodeName == "#comment") {
1278 Roo.log(node.tagName);
1279 if (!node.tagName.toLowerCase().match(/^(table|td|tr)$/)) {
1280 this.iterateChildren(node, this.cleanTableWidths);
1283 if (node.hasAttribute('width')) {
1284 node.removeAttribute('width');
1288 if (node.hasAttribute("style")) {
1291 var styles = node.getAttribute("style").split(";");
1293 Roo.each(styles, function(s) {
1294 if (!s.match(/:/)) {
1297 var kv = s.split(":");
1298 if (kv[0].match(/^\s*(width|min-width)\s*$/)) {
1301 // what ever is left... we allow.
1304 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1305 if (!nstyle.length) {
1306 node.removeAttribute('style');
1310 this.iterateChildren(node, this.cleanTableWidths);
1318 domToHTML : function(currentElement, depth, nopadtext) {
1321 nopadtext = nopadtext || false;
1323 if (!currentElement) {
1324 return this.domToHTML(this.doc.body);
1327 //Roo.log(currentElement);
1329 var allText = false;
1330 var nodeName = currentElement.nodeName;
1331 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1333 if (nodeName == '#text') {
1335 return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim();
1340 if (nodeName != 'BODY') {
1343 // Prints the node tagName, such as <A>, <IMG>, etc
1346 for(i = 0; i < currentElement.attributes.length;i++) {
1348 var aname = currentElement.attributes.item(i).name;
1349 if (!currentElement.attributes.item(i).value.length) {
1352 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1355 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1364 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1367 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1372 // Traverse the tree
1374 var currentElementChild = currentElement.childNodes.item(i);
1378 while (currentElementChild) {
1379 // Formatting code (indent the tree so it looks nice on the screen)
1380 var nopad = nopadtext;
1381 if (lastnode == 'SPAN') {
1385 if (currentElementChild.nodeName == '#text') {
1386 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1387 toadd = nopadtext ? toadd : toadd.trim();
1388 if (!nopad && toadd.length > 80) {
1389 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1394 currentElementChild = currentElement.childNodes.item(i);
1400 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1402 // Recursively traverse the tree structure of the child node
1403 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1404 lastnode = currentElementChild.nodeName;
1406 currentElementChild=currentElement.childNodes.item(i);
1412 // The remaining code is mostly for formatting the tree
1413 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1418 ret+= "</"+tagName+">";
1424 applyBlacklists : function()
1426 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1427 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1431 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1432 if (b.indexOf(tag) > -1) {
1435 this.white.push(tag);
1439 Roo.each(w, function(tag) {
1440 if (b.indexOf(tag) > -1) {
1443 if (this.white.indexOf(tag) > -1) {
1446 this.white.push(tag);
1451 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1452 if (w.indexOf(tag) > -1) {
1455 this.black.push(tag);
1459 Roo.each(b, function(tag) {
1460 if (w.indexOf(tag) > -1) {
1463 if (this.black.indexOf(tag) > -1) {
1466 this.black.push(tag);
1471 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1472 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1476 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1477 if (b.indexOf(tag) > -1) {
1480 this.cwhite.push(tag);
1484 Roo.each(w, function(tag) {
1485 if (b.indexOf(tag) > -1) {
1488 if (this.cwhite.indexOf(tag) > -1) {
1491 this.cwhite.push(tag);
1496 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1497 if (w.indexOf(tag) > -1) {
1500 this.cblack.push(tag);
1504 Roo.each(b, function(tag) {
1505 if (w.indexOf(tag) > -1) {
1508 if (this.cblack.indexOf(tag) > -1) {
1511 this.cblack.push(tag);
1516 setStylesheets : function(stylesheets)
1518 if(typeof(stylesheets) == 'string'){
1519 Roo.get(this.iframe.contentDocument.head).createChild({
1530 Roo.each(stylesheets, function(s) {
1535 Roo.get(_this.iframe.contentDocument.head).createChild({
1546 removeStylesheets : function()
1550 Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1555 setStyle : function(style)
1557 Roo.get(this.iframe.contentDocument.head).createChild({
1566 // hide stuff that is not compatible
1584 * @cfg {String} fieldClass @hide
1587 * @cfg {String} focusClass @hide
1590 * @cfg {String} autoCreate @hide
1593 * @cfg {String} inputType @hide
1596 * @cfg {String} invalidClass @hide
1599 * @cfg {String} invalidText @hide
1602 * @cfg {String} msgFx @hide
1605 * @cfg {String} validateOnBlur @hide
1609 Roo.HtmlEditorCore.white = [
1610 'area', 'br', 'img', 'input', 'hr', 'wbr',
1612 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1613 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1614 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1615 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1616 'table', 'ul', 'xmp',
1618 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1621 'dir', 'menu', 'ol', 'ul', 'dl',
1627 Roo.HtmlEditorCore.black = [
1628 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1630 'base', 'basefont', 'bgsound', 'blink', 'body',
1631 'frame', 'frameset', 'head', 'html', 'ilayer',
1632 'iframe', 'layer', 'link', 'meta', 'object',
1633 'script', 'style' ,'title', 'xml' // clean later..
1635 Roo.HtmlEditorCore.clean = [
1636 'script', 'style', 'title', 'xml'
1638 Roo.HtmlEditorCore.remove = [
1643 Roo.HtmlEditorCore.ablack = [
1647 Roo.HtmlEditorCore.aclean = [
1648 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1652 Roo.HtmlEditorCore.pwhite= [
1653 'http', 'https', 'mailto'
1656 // white listed style attributes.
1657 Roo.HtmlEditorCore.cwhite= [
1658 // 'text-align', /// default is to allow most things..
1664 // black listed style attributes.
1665 Roo.HtmlEditorCore.cblack= [
1666 // 'font-size' -- this can be set by the project
1670 Roo.HtmlEditorCore.swapCodes =[