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 } ' +
173 var cls = 'roo-htmleditor-body';
175 if(this.bodyCls.length){
176 cls += ' ' + this.bodyCls;
179 return '<html><head>' + st +
180 //<style type="text/css">' +
181 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
183 ' </head><body class="' + cls + '"></body></html>';
187 onRender : function(ct, position)
190 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
191 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
194 this.el.dom.style.border = '0 none';
195 this.el.dom.setAttribute('tabIndex', -1);
196 this.el.addClass('x-hidden hide');
200 if(Roo.isIE){ // fix IE 1px bogus margin
201 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
205 this.frameId = Roo.id();
209 var iframe = this.owner.wrap.createChild({
211 cls: 'form-control', // bootstrap..
215 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
220 this.iframe = iframe.dom;
224 this.doc.designMode = 'on';
227 this.doc.write(this.getDocMarkup());
231 var task = { // must defer to wait for browser to be ready
233 //console.log("run task?" + this.doc.readyState);
235 if(this.doc.body || this.doc.readyState == 'complete'){
237 this.doc.designMode="on";
241 Roo.TaskMgr.stop(task);
242 this.initEditor.defer(10, this);
249 Roo.TaskMgr.start(task);
254 onResize : function(w, h)
256 Roo.log('resize: ' +w + ',' + h );
257 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
261 if(typeof w == 'number'){
263 this.iframe.style.width = w + 'px';
265 if(typeof h == 'number'){
267 this.iframe.style.height = h + 'px';
269 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
276 * Toggles the editor between standard and source edit mode.
277 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
279 toggleSourceEdit : function(sourceEditMode){
281 this.sourceEditMode = sourceEditMode === true;
283 if(this.sourceEditMode){
285 Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
288 Roo.get(this.iframe).removeClass(['x-hidden','hide']);
289 //this.iframe.className = '';
292 //this.setSize(this.owner.wrap.getSize());
293 //this.fireEvent('editmodechange', this, this.sourceEditMode);
300 * Protected method that will not generally be called directly. If you need/want
301 * custom HTML cleanup, this is the method you should override.
302 * @param {String} html The HTML to be cleaned
303 * return {String} The cleaned HTML
305 cleanHtml : function(html){
308 if(Roo.isSafari){ // strip safari nonsense
309 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
312 if(html == ' '){
319 * HTML Editor -> Textarea
320 * Protected method that will not generally be called directly. Syncs the contents
321 * of the editor iframe with the textarea.
323 syncValue : function(){
324 if(this.initialized){
325 var bd = (this.doc.body || this.doc.documentElement);
326 //this.cleanUpPaste(); -- this is done else where and causes havoc..
327 var html = bd.innerHTML;
329 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
330 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
332 html = '<div style="'+m[0]+'">' + html + '</div>';
335 html = this.cleanHtml(html);
336 // fix up the special chars.. normaly like back quotes in word...
337 // however we do not want to do this with chinese..
338 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
339 var cc = b.charCodeAt();
341 (cc >= 0x4E00 && cc < 0xA000 ) ||
342 (cc >= 0x3400 && cc < 0x4E00 ) ||
343 (cc >= 0xf900 && cc < 0xfb00 )
349 if(this.owner.fireEvent('beforesync', this, html) !== false){
350 this.el.dom.value = html;
351 this.owner.fireEvent('sync', this, html);
357 * Protected method that will not generally be called directly. Pushes the value of the textarea
358 * into the iframe editor.
360 pushValue : function(){
361 if(this.initialized){
362 var v = this.el.dom.value.trim();
368 if(this.owner.fireEvent('beforepush', this, v) !== false){
369 var d = (this.doc.body || this.doc.documentElement);
372 this.el.dom.value = d.innerHTML;
373 this.owner.fireEvent('push', this, v);
379 deferFocus : function(){
380 this.focus.defer(10, this);
385 if(this.win && !this.sourceEditMode){
392 assignDocWin: function()
394 var iframe = this.iframe;
397 this.doc = iframe.contentWindow.document;
398 this.win = iframe.contentWindow;
400 // if (!Roo.get(this.frameId)) {
403 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
404 // this.win = Roo.get(this.frameId).dom.contentWindow;
406 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
410 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
411 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
416 initEditor : function(){
417 //console.log("INIT EDITOR");
422 this.doc.designMode="on";
424 this.doc.write(this.getDocMarkup());
427 var dbody = (this.doc.body || this.doc.documentElement);
428 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
429 // this copies styles from the containing element into thsi one..
430 // not sure why we need all of this..
431 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
433 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
434 //ss['background-attachment'] = 'fixed'; // w3c
435 dbody.bgProperties = 'fixed'; // ie
436 //Roo.DomHelper.applyStyles(dbody, ss);
437 Roo.EventManager.on(this.doc, {
438 //'mousedown': this.onEditorEvent,
439 'mouseup': this.onEditorEvent,
440 'dblclick': this.onEditorEvent,
441 'click': this.onEditorEvent,
442 'keyup': this.onEditorEvent,
447 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
449 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
450 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
452 this.initialized = true;
454 this.owner.fireEvent('initialize', this);
459 onDestroy : function(){
465 //for (var i =0; i < this.toolbars.length;i++) {
466 // // fixme - ask toolbars for heights?
467 // this.toolbars[i].onDestroy();
470 //this.wrap.dom.innerHTML = '';
471 //this.wrap.remove();
476 onFirstFocus : function(){
481 this.activated = true;
484 if(Roo.isGecko){ // prevent silly gecko errors
486 var s = this.win.getSelection();
487 if(!s.focusNode || s.focusNode.nodeType != 3){
488 var r = s.getRangeAt(0);
489 r.selectNodeContents((this.doc.body || this.doc.documentElement));
494 this.execCmd('useCSS', true);
495 this.execCmd('styleWithCSS', false);
498 this.owner.fireEvent('activate', this);
502 adjustFont: function(btn){
503 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
504 //if(Roo.isSafari){ // safari
507 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
508 if(Roo.isSafari){ // safari
509 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
510 v = (v < 10) ? 10 : v;
511 v = (v > 48) ? 48 : v;
512 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
517 v = Math.max(1, v+adjust);
519 this.execCmd('FontSize', v );
522 onEditorEvent : function(e)
524 this.owner.fireEvent('editorevent', this, e);
525 // this.updateToolbar();
526 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
529 insertTag : function(tg)
531 // could be a bit smarter... -> wrap the current selected tRoo..
532 if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
534 range = this.createRange(this.getSelection());
535 var wrappingNode = this.doc.createElement(tg.toLowerCase());
536 wrappingNode.appendChild(range.extractContents());
537 range.insertNode(wrappingNode);
544 this.execCmd("formatblock", tg);
548 insertText : function(txt)
552 var range = this.createRange();
553 range.deleteContents();
554 //alert(Sender.getAttribute('label'));
556 range.insertNode(this.doc.createTextNode(txt));
562 * Executes a Midas editor command on the editor document and performs necessary focus and
563 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
564 * @param {String} cmd The Midas command
565 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
567 relayCmd : function(cmd, value){
569 this.execCmd(cmd, value);
570 this.owner.fireEvent('editorevent', this);
571 //this.updateToolbar();
572 this.owner.deferFocus();
576 * Executes a Midas editor command directly on the editor document.
577 * For visual commands, you should use {@link #relayCmd} instead.
578 * <b>This should only be called after the editor is initialized.</b>
579 * @param {String} cmd The Midas command
580 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
582 execCmd : function(cmd, value){
583 this.doc.execCommand(cmd, false, value === undefined ? null : value);
590 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
592 * @param {String} text | dom node..
594 insertAtCursor : function(text)
603 var r = this.doc.selection.createRange();
614 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
618 // from jquery ui (MIT licenced)
622 if (win.getSelection && win.getSelection().getRangeAt) {
623 range = win.getSelection().getRangeAt(0);
624 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
625 range.insertNode(node);
626 } else if (win.document.selection && win.document.selection.createRange) {
627 // no firefox support
628 var txt = typeof(text) == 'string' ? text : text.outerHTML;
629 win.document.selection.createRange().pasteHTML(txt);
631 // no firefox support
632 var txt = typeof(text) == 'string' ? text : text.outerHTML;
633 this.execCmd('InsertHTML', txt);
642 mozKeyPress : function(e){
644 var c = e.getCharCode(), cmd;
647 c = String.fromCharCode(c).toLowerCase();
661 this.cleanUpPaste.defer(100, this);
677 fixKeys : function(){ // load time branching for fastest keydown performance
680 var k = e.getKey(), r;
683 r = this.doc.selection.createRange();
686 r.pasteHTML('    ');
693 r = this.doc.selection.createRange();
695 var target = r.parentElement();
696 if(!target || target.tagName.toLowerCase() != 'li'){
698 r.pasteHTML('<br />');
704 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
705 this.cleanUpPaste.defer(100, this);
711 }else if(Roo.isOpera){
717 this.execCmd('InsertHTML','    ');
720 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
721 this.cleanUpPaste.defer(100, this);
726 }else if(Roo.isSafari){
732 this.execCmd('InsertText','\t');
736 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
737 this.cleanUpPaste.defer(100, this);
745 getAllAncestors: function()
747 var p = this.getSelectedNode();
750 a.push(p); // push blank onto stack..
751 p = this.getParentElement();
755 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
759 a.push(this.doc.body);
766 getSelection : function()
769 return Roo.isIE ? this.doc.selection : this.win.getSelection();
772 getSelectedNode: function()
774 // this may only work on Gecko!!!
776 // should we cache this!!!!
781 var range = this.createRange(this.getSelection()).cloneRange();
784 var parent = range.parentElement();
786 var testRange = range.duplicate();
787 testRange.moveToElementText(parent);
788 if (testRange.inRange(range)) {
791 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
794 parent = parent.parentElement;
799 // is ancestor a text element.
800 var ac = range.commonAncestorContainer;
801 if (ac.nodeType == 3) {
805 var ar = ac.childNodes;
808 var other_nodes = [];
809 var has_other_nodes = false;
810 for (var i=0;i<ar.length;i++) {
811 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
814 // fullly contained node.
816 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
821 // probably selected..
822 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
823 other_nodes.push(ar[i]);
827 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
832 has_other_nodes = true;
834 if (!nodes.length && other_nodes.length) {
837 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
843 createRange: function(sel)
845 // this has strange effects when using with
846 // top toolbar - not sure if it's a great idea.
847 //this.editor.contentWindow.focus();
848 if (typeof sel != "undefined") {
850 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
852 return this.doc.createRange();
855 return this.doc.createRange();
858 getParentElement: function()
862 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
864 var range = this.createRange(sel);
867 var p = range.commonAncestorContainer;
868 while (p.nodeType == 3) { // text node
879 * Range intersection.. the hard stuff...
883 * [ -- selected range --- ]
887 * if end is before start or hits it. fail.
888 * if start is after end or hits it fail.
890 * if either hits (but other is outside. - then it's not
896 // @see http://www.thismuchiknow.co.uk/?p=64.
897 rangeIntersectsNode : function(range, node)
899 var nodeRange = node.ownerDocument.createRange();
901 nodeRange.selectNode(node);
903 nodeRange.selectNodeContents(node);
906 var rangeStartRange = range.cloneRange();
907 rangeStartRange.collapse(true);
909 var rangeEndRange = range.cloneRange();
910 rangeEndRange.collapse(false);
912 var nodeStartRange = nodeRange.cloneRange();
913 nodeStartRange.collapse(true);
915 var nodeEndRange = nodeRange.cloneRange();
916 nodeEndRange.collapse(false);
918 return rangeStartRange.compareBoundaryPoints(
919 Range.START_TO_START, nodeEndRange) == -1 &&
920 rangeEndRange.compareBoundaryPoints(
921 Range.START_TO_START, nodeStartRange) == 1;
925 rangeCompareNode : function(range, node)
927 var nodeRange = node.ownerDocument.createRange();
929 nodeRange.selectNode(node);
931 nodeRange.selectNodeContents(node);
935 range.collapse(true);
937 nodeRange.collapse(true);
939 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
940 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
942 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
944 var nodeIsBefore = ss == 1;
945 var nodeIsAfter = ee == -1;
947 if (nodeIsBefore && nodeIsAfter) {
950 if (!nodeIsBefore && nodeIsAfter) {
951 return 1; //right trailed.
954 if (nodeIsBefore && !nodeIsAfter) {
955 return 2; // left trailed.
961 // private? - in a new class?
962 cleanUpPaste : function()
964 // cleans up the whole document..
965 Roo.log('cleanuppaste');
967 this.cleanUpChildren(this.doc.body);
968 var clean = this.cleanWordChars(this.doc.body.innerHTML);
969 if (clean != this.doc.body.innerHTML) {
970 this.doc.body.innerHTML = clean;
975 cleanWordChars : function(input) {// change the chars to hex code
976 var he = Roo.HtmlEditorCore;
979 Roo.each(he.swapCodes, function(sw) {
980 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
982 output = output.replace(swapper, sw[1]);
989 cleanUpChildren : function (n)
991 if (!n.childNodes.length) {
994 for (var i = n.childNodes.length-1; i > -1 ; i--) {
995 this.cleanUpChild(n.childNodes[i]);
1002 cleanUpChild : function (node)
1005 //console.log(node);
1006 if (node.nodeName == "#text") {
1007 // clean up silly Windows -- stuff?
1010 if (node.nodeName == "#comment") {
1011 node.parentNode.removeChild(node);
1012 // clean up silly Windows -- stuff?
1015 var lcname = node.tagName.toLowerCase();
1016 // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full
1017 // whitelist of tags..
1019 if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
1021 node.parentNode.removeChild(node);
1026 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1028 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1029 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1031 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1032 // remove_keep_children = true;
1035 if (remove_keep_children) {
1036 this.cleanUpChildren(node);
1037 // inserts everything just before this node...
1038 while (node.childNodes.length) {
1039 var cn = node.childNodes[0];
1040 node.removeChild(cn);
1041 node.parentNode.insertBefore(cn, node);
1043 node.parentNode.removeChild(node);
1047 if (!node.attributes || !node.attributes.length) {
1048 this.cleanUpChildren(node);
1052 function cleanAttr(n,v)
1055 if (v.match(/^\./) || v.match(/^\//)) {
1058 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/) || v.match(/^ftp:/)) {
1061 if (v.match(/^#/)) {
1064 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1065 node.removeAttribute(n);
1069 var cwhite = this.cwhite;
1070 var cblack = this.cblack;
1072 function cleanStyle(n,v)
1074 if (v.match(/expression/)) { //XSS?? should we even bother..
1075 node.removeAttribute(n);
1079 var parts = v.split(/;/);
1082 Roo.each(parts, function(p) {
1083 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1087 var l = p.split(':').shift().replace(/\s+/g,'');
1088 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1090 if ( cwhite.length && cblack.indexOf(l) > -1) {
1091 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1092 //node.removeAttribute(n);
1096 // only allow 'c whitelisted system attributes'
1097 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1098 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1099 //node.removeAttribute(n);
1110 node.setAttribute(n, clean.join(';'));
1112 node.removeAttribute(n);
1118 for (var i = node.attributes.length-1; i > -1 ; i--) {
1119 var a = node.attributes[i];
1122 if (a.name.toLowerCase().substr(0,2)=='on') {
1123 node.removeAttribute(a.name);
1126 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1127 node.removeAttribute(a.name);
1130 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1131 cleanAttr(a.name,a.value); // fixme..
1134 if (a.name == 'style') {
1135 cleanStyle(a.name,a.value);
1138 /// clean up MS crap..
1139 // tecnically this should be a list of valid class'es..
1142 if (a.name == 'class') {
1143 if (a.value.match(/^Mso/)) {
1144 node.className = '';
1147 if (a.value.match(/^body$/)) {
1148 node.className = '';
1159 this.cleanUpChildren(node);
1165 * Clean up MS wordisms...
1167 cleanWord : function(node)
1170 this.cleanWord(this.doc.body);
1175 node.nodeName == 'SPAN' &&
1176 !node.hasAttributes() &&
1177 node.childNodes.length == 1 &&
1178 node.firstChild.nodeName == "#text"
1180 var text = ' ' + node.innerHTML + ' ';
1181 var textNode = document.createTextNode(text);
1182 node.parentNode.insertBefore(textNode, node);
1183 node.parentNode.removeChild(node);
1186 if (node.nodeName == "#text") {
1187 // clean up silly Windows -- stuff?
1190 if (node.nodeName == "#comment") {
1191 node.parentNode.removeChild(node);
1192 // clean up silly Windows -- stuff?
1196 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1197 node.parentNode.removeChild(node);
1201 // remove - but keep children..
1202 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1203 while (node.childNodes.length) {
1204 var cn = node.childNodes[0];
1205 node.removeChild(cn);
1206 node.parentNode.insertBefore(cn, node);
1208 node.parentNode.removeChild(node);
1209 this.iterateChildren(node, this.cleanWord);
1213 if (node.className.length) {
1215 var cn = node.className.split(/\W+/);
1217 Roo.each(cn, function(cls) {
1218 if (cls.match(/Mso[a-zA-Z]+/)) {
1223 node.className = cna.length ? cna.join(' ') : '';
1225 node.removeAttribute("class");
1229 if (node.hasAttribute("lang")) {
1230 node.removeAttribute("lang");
1233 if (node.hasAttribute("style")) {
1235 var styles = node.getAttribute("style").split(";");
1237 Roo.each(styles, function(s) {
1238 if (!s.match(/:/)) {
1241 var kv = s.split(":");
1242 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1245 // what ever is left... we allow.
1248 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1249 if (!nstyle.length) {
1250 node.removeAttribute('style');
1253 this.iterateChildren(node, this.cleanWord);
1259 * iterateChildren of a Node, calling fn each time, using this as the scole..
1260 * @param {DomNode} node node to iterate children of.
1261 * @param {Function} fn method of this class to call on each item.
1263 iterateChildren : function(node, fn)
1265 if (!node.childNodes.length) {
1268 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1269 fn.call(this, node.childNodes[i])
1277 * Quite often pasting from word etc.. results in tables with column and widths.
1278 * This does not work well on fluid HTML layouts - like emails. - so this code should hunt an destroy them..
1281 cleanTableWidths : function(node)
1286 this.cleanTableWidths(this.doc.body);
1291 if (node.nodeName == "#text" || node.nodeName == "#comment") {
1294 Roo.log(node.tagName);
1295 if (!node.tagName.toLowerCase().match(/^(table|td|tr)$/)) {
1296 this.iterateChildren(node, this.cleanTableWidths);
1299 if (node.hasAttribute('width')) {
1300 node.removeAttribute('width');
1304 if (node.hasAttribute("style")) {
1307 var styles = node.getAttribute("style").split(";");
1309 Roo.each(styles, function(s) {
1310 if (!s.match(/:/)) {
1313 var kv = s.split(":");
1314 if (kv[0].match(/^\s*(width|min-width)\s*$/)) {
1317 // what ever is left... we allow.
1320 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1321 if (!nstyle.length) {
1322 node.removeAttribute('style');
1326 this.iterateChildren(node, this.cleanTableWidths);
1334 domToHTML : function(currentElement, depth, nopadtext) {
1337 nopadtext = nopadtext || false;
1339 if (!currentElement) {
1340 return this.domToHTML(this.doc.body);
1343 //Roo.log(currentElement);
1345 var allText = false;
1346 var nodeName = currentElement.nodeName;
1347 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1349 if (nodeName == '#text') {
1351 return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim();
1356 if (nodeName != 'BODY') {
1359 // Prints the node tagName, such as <A>, <IMG>, etc
1362 for(i = 0; i < currentElement.attributes.length;i++) {
1364 var aname = currentElement.attributes.item(i).name;
1365 if (!currentElement.attributes.item(i).value.length) {
1368 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1371 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1380 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1383 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1388 // Traverse the tree
1390 var currentElementChild = currentElement.childNodes.item(i);
1394 while (currentElementChild) {
1395 // Formatting code (indent the tree so it looks nice on the screen)
1396 var nopad = nopadtext;
1397 if (lastnode == 'SPAN') {
1401 if (currentElementChild.nodeName == '#text') {
1402 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1403 toadd = nopadtext ? toadd : toadd.trim();
1404 if (!nopad && toadd.length > 80) {
1405 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1410 currentElementChild = currentElement.childNodes.item(i);
1416 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1418 // Recursively traverse the tree structure of the child node
1419 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1420 lastnode = currentElementChild.nodeName;
1422 currentElementChild=currentElement.childNodes.item(i);
1428 // The remaining code is mostly for formatting the tree
1429 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1434 ret+= "</"+tagName+">";
1440 applyBlacklists : function()
1442 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1443 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1447 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1448 if (b.indexOf(tag) > -1) {
1451 this.white.push(tag);
1455 Roo.each(w, function(tag) {
1456 if (b.indexOf(tag) > -1) {
1459 if (this.white.indexOf(tag) > -1) {
1462 this.white.push(tag);
1467 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1468 if (w.indexOf(tag) > -1) {
1471 this.black.push(tag);
1475 Roo.each(b, function(tag) {
1476 if (w.indexOf(tag) > -1) {
1479 if (this.black.indexOf(tag) > -1) {
1482 this.black.push(tag);
1487 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1488 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1492 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1493 if (b.indexOf(tag) > -1) {
1496 this.cwhite.push(tag);
1500 Roo.each(w, function(tag) {
1501 if (b.indexOf(tag) > -1) {
1504 if (this.cwhite.indexOf(tag) > -1) {
1507 this.cwhite.push(tag);
1512 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1513 if (w.indexOf(tag) > -1) {
1516 this.cblack.push(tag);
1520 Roo.each(b, function(tag) {
1521 if (w.indexOf(tag) > -1) {
1524 if (this.cblack.indexOf(tag) > -1) {
1527 this.cblack.push(tag);
1532 setStylesheets : function(stylesheets)
1534 if(typeof(stylesheets) == 'string'){
1535 Roo.get(this.iframe.contentDocument.head).createChild({
1546 Roo.each(stylesheets, function(s) {
1551 Roo.get(_this.iframe.contentDocument.head).createChild({
1562 removeStylesheets : function()
1566 Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1571 setStyle : function(style)
1573 Roo.get(this.iframe.contentDocument.head).createChild({
1582 // hide stuff that is not compatible
1600 * @cfg {String} fieldClass @hide
1603 * @cfg {String} focusClass @hide
1606 * @cfg {String} autoCreate @hide
1609 * @cfg {String} inputType @hide
1612 * @cfg {String} invalidClass @hide
1615 * @cfg {String} invalidText @hide
1618 * @cfg {String} msgFx @hide
1621 * @cfg {String} validateOnBlur @hide
1625 Roo.HtmlEditorCore.white = [
1626 'area', 'br', 'img', 'input', 'hr', 'wbr',
1628 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1629 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1630 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1631 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1632 'table', 'ul', 'xmp',
1634 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1637 'dir', 'menu', 'ol', 'ul', 'dl',
1643 Roo.HtmlEditorCore.black = [
1644 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1646 'base', 'basefont', 'bgsound', 'blink', 'body',
1647 'frame', 'frameset', 'head', 'html', 'ilayer',
1648 'iframe', 'layer', 'link', 'meta', 'object',
1649 'script', 'style' ,'title', 'xml' // clean later..
1651 Roo.HtmlEditorCore.clean = [
1652 'script', 'style', 'title', 'xml'
1654 Roo.HtmlEditorCore.remove = [
1659 Roo.HtmlEditorCore.ablack = [
1663 Roo.HtmlEditorCore.aclean = [
1664 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1668 Roo.HtmlEditorCore.pwhite= [
1669 'http', 'https', 'mailto'
1672 // white listed style attributes.
1673 Roo.HtmlEditorCore.cwhite= [
1674 // 'text-align', /// default is to allow most things..
1680 // black listed style attributes.
1681 Roo.HtmlEditorCore.cblack= [
1682 // 'font-size' -- this can be set by the project
1686 Roo.HtmlEditorCore.swapCodes =[