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;}' +
167 st += '<style type="text/css">' +
168 'IMG { cursor: pointer } ' +
172 return '<html><head>' + st +
173 //<style type="text/css">' +
174 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
176 ' </head><body class="roo-htmleditor-body"></body></html>';
180 onRender : function(ct, position)
183 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
184 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
187 this.el.dom.style.border = '0 none';
188 this.el.dom.setAttribute('tabIndex', -1);
189 this.el.addClass('x-hidden hide');
193 if(Roo.isIE){ // fix IE 1px bogus margin
194 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
198 this.frameId = Roo.id();
202 var iframe = this.owner.wrap.createChild({
204 cls: 'form-control', // bootstrap..
208 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
213 this.iframe = iframe.dom;
217 this.doc.designMode = 'on';
220 this.doc.write(this.getDocMarkup());
224 var task = { // must defer to wait for browser to be ready
226 //console.log("run task?" + this.doc.readyState);
228 if(this.doc.body || this.doc.readyState == 'complete'){
230 this.doc.designMode="on";
234 Roo.TaskMgr.stop(task);
235 this.initEditor.defer(10, this);
242 Roo.TaskMgr.start(task);
247 onResize : function(w, h)
249 Roo.log('resize: ' +w + ',' + h );
250 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
254 if(typeof w == 'number'){
256 this.iframe.style.width = w + 'px';
258 if(typeof h == 'number'){
260 this.iframe.style.height = h + 'px';
262 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
269 * Toggles the editor between standard and source edit mode.
270 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
272 toggleSourceEdit : function(sourceEditMode){
274 this.sourceEditMode = sourceEditMode === true;
276 if(this.sourceEditMode){
278 Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
281 Roo.get(this.iframe).removeClass(['x-hidden','hide']);
282 //this.iframe.className = '';
285 //this.setSize(this.owner.wrap.getSize());
286 //this.fireEvent('editmodechange', this, this.sourceEditMode);
293 * Protected method that will not generally be called directly. If you need/want
294 * custom HTML cleanup, this is the method you should override.
295 * @param {String} html The HTML to be cleaned
296 * return {String} The cleaned HTML
298 cleanHtml : function(html){
301 if(Roo.isSafari){ // strip safari nonsense
302 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
305 if(html == ' '){
312 * HTML Editor -> Textarea
313 * Protected method that will not generally be called directly. Syncs the contents
314 * of the editor iframe with the textarea.
316 syncValue : function(){
317 if(this.initialized){
318 var bd = (this.doc.body || this.doc.documentElement);
319 //this.cleanUpPaste(); -- this is done else where and causes havoc..
320 var html = bd.innerHTML;
322 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
323 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
325 html = '<div style="'+m[0]+'">' + html + '</div>';
328 html = this.cleanHtml(html);
329 // fix up the special chars.. normaly like back quotes in word...
330 // however we do not want to do this with chinese..
331 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
332 var cc = b.charCodeAt();
334 (cc >= 0x4E00 && cc < 0xA000 ) ||
335 (cc >= 0x3400 && cc < 0x4E00 ) ||
336 (cc >= 0xf900 && cc < 0xfb00 )
342 if(this.owner.fireEvent('beforesync', this, html) !== false){
343 this.el.dom.value = html;
344 this.owner.fireEvent('sync', this, html);
350 * Protected method that will not generally be called directly. Pushes the value of the textarea
351 * into the iframe editor.
353 pushValue : function(){
354 if(this.initialized){
355 var v = this.el.dom.value.trim();
361 if(this.owner.fireEvent('beforepush', this, v) !== false){
362 var d = (this.doc.body || this.doc.documentElement);
365 this.el.dom.value = d.innerHTML;
366 this.owner.fireEvent('push', this, v);
372 deferFocus : function(){
373 this.focus.defer(10, this);
378 if(this.win && !this.sourceEditMode){
385 assignDocWin: function()
387 var iframe = this.iframe;
390 this.doc = iframe.contentWindow.document;
391 this.win = iframe.contentWindow;
393 // if (!Roo.get(this.frameId)) {
396 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
397 // this.win = Roo.get(this.frameId).dom.contentWindow;
399 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
403 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
404 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
409 initEditor : function(){
410 //console.log("INIT EDITOR");
415 this.doc.designMode="on";
417 this.doc.write(this.getDocMarkup());
420 var dbody = (this.doc.body || this.doc.documentElement);
421 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
422 // this copies styles from the containing element into thsi one..
423 // not sure why we need all of this..
424 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
426 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
427 //ss['background-attachment'] = 'fixed'; // w3c
428 dbody.bgProperties = 'fixed'; // ie
429 //Roo.DomHelper.applyStyles(dbody, ss);
430 Roo.EventManager.on(this.doc, {
431 //'mousedown': this.onEditorEvent,
432 'mouseup': this.onEditorEvent,
433 'dblclick': this.onEditorEvent,
434 'click': this.onEditorEvent,
435 'keyup': this.onEditorEvent,
440 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
442 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
443 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
445 this.initialized = true;
447 this.owner.fireEvent('initialize', this);
452 onDestroy : function(){
458 //for (var i =0; i < this.toolbars.length;i++) {
459 // // fixme - ask toolbars for heights?
460 // this.toolbars[i].onDestroy();
463 //this.wrap.dom.innerHTML = '';
464 //this.wrap.remove();
469 onFirstFocus : function(){
474 this.activated = true;
477 if(Roo.isGecko){ // prevent silly gecko errors
479 var s = this.win.getSelection();
480 if(!s.focusNode || s.focusNode.nodeType != 3){
481 var r = s.getRangeAt(0);
482 r.selectNodeContents((this.doc.body || this.doc.documentElement));
487 this.execCmd('useCSS', true);
488 this.execCmd('styleWithCSS', false);
491 this.owner.fireEvent('activate', this);
495 adjustFont: function(btn){
496 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
497 //if(Roo.isSafari){ // safari
500 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
501 if(Roo.isSafari){ // safari
502 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
503 v = (v < 10) ? 10 : v;
504 v = (v > 48) ? 48 : v;
505 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
510 v = Math.max(1, v+adjust);
512 this.execCmd('FontSize', v );
515 onEditorEvent : function(e)
517 this.owner.fireEvent('editorevent', this, e);
518 // this.updateToolbar();
519 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
522 insertTag : function(tg)
524 // could be a bit smarter... -> wrap the current selected tRoo..
525 if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
527 range = this.createRange(this.getSelection());
528 var wrappingNode = this.doc.createElement(tg.toLowerCase());
529 wrappingNode.appendChild(range.extractContents());
530 range.insertNode(wrappingNode);
537 this.execCmd("formatblock", tg);
541 insertText : function(txt)
545 var range = this.createRange();
546 range.deleteContents();
547 //alert(Sender.getAttribute('label'));
549 range.insertNode(this.doc.createTextNode(txt));
555 * Executes a Midas editor command on the editor document and performs necessary focus and
556 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
557 * @param {String} cmd The Midas command
558 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
560 relayCmd : function(cmd, value){
562 this.execCmd(cmd, value);
563 this.owner.fireEvent('editorevent', this);
564 //this.updateToolbar();
565 this.owner.deferFocus();
569 * Executes a Midas editor command directly on the editor document.
570 * For visual commands, you should use {@link #relayCmd} instead.
571 * <b>This should only be called after the editor is initialized.</b>
572 * @param {String} cmd The Midas command
573 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
575 execCmd : function(cmd, value){
576 this.doc.execCommand(cmd, false, value === undefined ? null : value);
583 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
585 * @param {String} text | dom node..
587 insertAtCursor : function(text)
589 Roo.log('insert at cursorrrrrrrrrr');
593 Roo.log('not activate');
599 var r = this.doc.selection.createRange();
610 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
614 // from jquery ui (MIT licenced)
618 if (win.getSelection && win.getSelection().getRangeAt) {
619 range = win.getSelection().getRangeAt(0);
620 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
621 range.insertNode(node);
622 } else if (win.document.selection && win.document.selection.createRange) {
623 // no firefox support
624 var txt = typeof(text) == 'string' ? text : text.outerHTML;
625 win.document.selection.createRange().pasteHTML(txt);
627 // no firefox support
628 var txt = typeof(text) == 'string' ? text : text.outerHTML;
629 this.execCmd('InsertHTML', txt);
638 mozKeyPress : function(e){
640 var c = e.getCharCode(), cmd;
643 c = String.fromCharCode(c).toLowerCase();
657 this.cleanUpPaste.defer(100, this);
673 fixKeys : function(){ // load time branching for fastest keydown performance
676 var k = e.getKey(), r;
679 r = this.doc.selection.createRange();
682 r.pasteHTML('    ');
689 r = this.doc.selection.createRange();
691 var target = r.parentElement();
692 if(!target || target.tagName.toLowerCase() != 'li'){
694 r.pasteHTML('<br />');
700 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
701 this.cleanUpPaste.defer(100, this);
707 }else if(Roo.isOpera){
713 this.execCmd('InsertHTML','    ');
716 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
717 this.cleanUpPaste.defer(100, this);
722 }else if(Roo.isSafari){
728 this.execCmd('InsertText','\t');
732 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
733 this.cleanUpPaste.defer(100, this);
741 getAllAncestors: function()
743 var p = this.getSelectedNode();
746 a.push(p); // push blank onto stack..
747 p = this.getParentElement();
751 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
755 a.push(this.doc.body);
762 getSelection : function()
765 return Roo.isIE ? this.doc.selection : this.win.getSelection();
768 getSelectedNode: function()
770 // this may only work on Gecko!!!
772 // should we cache this!!!!
777 var range = this.createRange(this.getSelection()).cloneRange();
780 var parent = range.parentElement();
782 var testRange = range.duplicate();
783 testRange.moveToElementText(parent);
784 if (testRange.inRange(range)) {
787 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
790 parent = parent.parentElement;
795 // is ancestor a text element.
796 var ac = range.commonAncestorContainer;
797 if (ac.nodeType == 3) {
801 var ar = ac.childNodes;
804 var other_nodes = [];
805 var has_other_nodes = false;
806 for (var i=0;i<ar.length;i++) {
807 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
810 // fullly contained node.
812 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
817 // probably selected..
818 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
819 other_nodes.push(ar[i]);
823 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
828 has_other_nodes = true;
830 if (!nodes.length && other_nodes.length) {
833 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
839 createRange: function(sel)
841 // this has strange effects when using with
842 // top toolbar - not sure if it's a great idea.
843 //this.editor.contentWindow.focus();
844 if (typeof sel != "undefined") {
846 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
848 return this.doc.createRange();
851 return this.doc.createRange();
854 getParentElement: function()
858 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
860 var range = this.createRange(sel);
863 var p = range.commonAncestorContainer;
864 while (p.nodeType == 3) { // text node
875 * Range intersection.. the hard stuff...
879 * [ -- selected range --- ]
883 * if end is before start or hits it. fail.
884 * if start is after end or hits it fail.
886 * if either hits (but other is outside. - then it's not
892 // @see http://www.thismuchiknow.co.uk/?p=64.
893 rangeIntersectsNode : function(range, node)
895 var nodeRange = node.ownerDocument.createRange();
897 nodeRange.selectNode(node);
899 nodeRange.selectNodeContents(node);
902 var rangeStartRange = range.cloneRange();
903 rangeStartRange.collapse(true);
905 var rangeEndRange = range.cloneRange();
906 rangeEndRange.collapse(false);
908 var nodeStartRange = nodeRange.cloneRange();
909 nodeStartRange.collapse(true);
911 var nodeEndRange = nodeRange.cloneRange();
912 nodeEndRange.collapse(false);
914 return rangeStartRange.compareBoundaryPoints(
915 Range.START_TO_START, nodeEndRange) == -1 &&
916 rangeEndRange.compareBoundaryPoints(
917 Range.START_TO_START, nodeStartRange) == 1;
921 rangeCompareNode : function(range, node)
923 var nodeRange = node.ownerDocument.createRange();
925 nodeRange.selectNode(node);
927 nodeRange.selectNodeContents(node);
931 range.collapse(true);
933 nodeRange.collapse(true);
935 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
936 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
938 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
940 var nodeIsBefore = ss == 1;
941 var nodeIsAfter = ee == -1;
943 if (nodeIsBefore && nodeIsAfter) {
946 if (!nodeIsBefore && nodeIsAfter) {
947 return 1; //right trailed.
950 if (nodeIsBefore && !nodeIsAfter) {
951 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?
1011 var lcname = node.tagName.toLowerCase();
1012 // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full
1013 // whitelist of tags..
1015 if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
1017 node.parentNode.removeChild(node);
1022 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1024 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1025 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1027 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1028 // remove_keep_children = true;
1031 if (remove_keep_children) {
1032 this.cleanUpChildren(node);
1033 // inserts everything just before this node...
1034 while (node.childNodes.length) {
1035 var cn = node.childNodes[0];
1036 node.removeChild(cn);
1037 node.parentNode.insertBefore(cn, node);
1039 node.parentNode.removeChild(node);
1043 if (!node.attributes || !node.attributes.length) {
1044 this.cleanUpChildren(node);
1048 function cleanAttr(n,v)
1051 if (v.match(/^\./) || v.match(/^\//)) {
1054 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1057 if (v.match(/^#/)) {
1060 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1061 node.removeAttribute(n);
1065 var cwhite = this.cwhite;
1066 var cblack = this.cblack;
1068 function cleanStyle(n,v)
1070 if (v.match(/expression/)) { //XSS?? should we even bother..
1071 node.removeAttribute(n);
1075 var parts = v.split(/;/);
1078 Roo.each(parts, function(p) {
1079 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1083 var l = p.split(':').shift().replace(/\s+/g,'');
1084 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1086 if ( cwhite.length && cblack.indexOf(l) > -1) {
1087 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1088 //node.removeAttribute(n);
1092 // only allow 'c whitelisted system attributes'
1093 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1094 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1095 //node.removeAttribute(n);
1106 node.setAttribute(n, clean.join(';'));
1108 node.removeAttribute(n);
1114 for (var i = node.attributes.length-1; i > -1 ; i--) {
1115 var a = node.attributes[i];
1118 if (a.name.toLowerCase().substr(0,2)=='on') {
1119 node.removeAttribute(a.name);
1122 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1123 node.removeAttribute(a.name);
1126 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1127 cleanAttr(a.name,a.value); // fixme..
1130 if (a.name == 'style') {
1131 cleanStyle(a.name,a.value);
1134 /// clean up MS crap..
1135 // tecnically this should be a list of valid class'es..
1138 if (a.name == 'class') {
1139 if (a.value.match(/^Mso/)) {
1140 node.className = '';
1143 if (a.value.match(/body/)) {
1144 node.className = '';
1155 this.cleanUpChildren(node);
1161 * Clean up MS wordisms...
1163 cleanWord : function(node)
1168 this.cleanWord(this.doc.body);
1171 if (node.nodeName == "#text") {
1172 // clean up silly Windows -- stuff?
1175 if (node.nodeName == "#comment") {
1176 node.parentNode.removeChild(node);
1177 // clean up silly Windows -- stuff?
1181 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1182 node.parentNode.removeChild(node);
1186 // remove - but keep children..
1187 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1188 while (node.childNodes.length) {
1189 var cn = node.childNodes[0];
1190 node.removeChild(cn);
1191 node.parentNode.insertBefore(cn, node);
1193 node.parentNode.removeChild(node);
1194 this.iterateChildren(node, this.cleanWord);
1198 if (node.className.length) {
1200 var cn = node.className.split(/\W+/);
1202 Roo.each(cn, function(cls) {
1203 if (cls.match(/Mso[a-zA-Z]+/)) {
1208 node.className = cna.length ? cna.join(' ') : '';
1210 node.removeAttribute("class");
1214 if (node.hasAttribute("lang")) {
1215 node.removeAttribute("lang");
1218 if (node.hasAttribute("style")) {
1220 var styles = node.getAttribute("style").split(";");
1222 Roo.each(styles, function(s) {
1223 if (!s.match(/:/)) {
1226 var kv = s.split(":");
1227 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1230 // what ever is left... we allow.
1233 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1234 if (!nstyle.length) {
1235 node.removeAttribute('style');
1238 this.iterateChildren(node, this.cleanWord);
1244 * iterateChildren of a Node, calling fn each time, using this as the scole..
1245 * @param {DomNode} node node to iterate children of.
1246 * @param {Function} fn method of this class to call on each item.
1248 iterateChildren : function(node, fn)
1250 if (!node.childNodes.length) {
1253 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1254 fn.call(this, node.childNodes[i])
1262 * Quite often pasting from word etc.. results in tables with column and widths.
1263 * This does not work well on fluid HTML layouts - like emails. - so this code should hunt an destroy them..
1266 cleanTableWidths : function(node)
1271 this.cleanTableWidths(this.doc.body);
1276 if (node.nodeName == "#text" || node.nodeName == "#comment") {
1279 Roo.log(node.tagName);
1280 if (!node.tagName.toLowerCase().match(/^(table|td|tr)$/)) {
1281 this.iterateChildren(node, this.cleanTableWidths);
1284 if (node.hasAttribute('width')) {
1285 node.removeAttribute('width');
1289 if (node.hasAttribute("style")) {
1292 var styles = node.getAttribute("style").split(";");
1294 Roo.each(styles, function(s) {
1295 if (!s.match(/:/)) {
1298 var kv = s.split(":");
1299 if (kv[0].match(/^\s*(width|min-width)\s*$/)) {
1302 // what ever is left... we allow.
1305 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1306 if (!nstyle.length) {
1307 node.removeAttribute('style');
1311 this.iterateChildren(node, this.cleanTableWidths);
1319 domToHTML : function(currentElement, depth, nopadtext) {
1322 nopadtext = nopadtext || false;
1324 if (!currentElement) {
1325 return this.domToHTML(this.doc.body);
1328 //Roo.log(currentElement);
1330 var allText = false;
1331 var nodeName = currentElement.nodeName;
1332 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1334 if (nodeName == '#text') {
1336 return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim();
1341 if (nodeName != 'BODY') {
1344 // Prints the node tagName, such as <A>, <IMG>, etc
1347 for(i = 0; i < currentElement.attributes.length;i++) {
1349 var aname = currentElement.attributes.item(i).name;
1350 if (!currentElement.attributes.item(i).value.length) {
1353 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1356 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1365 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1368 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1373 // Traverse the tree
1375 var currentElementChild = currentElement.childNodes.item(i);
1379 while (currentElementChild) {
1380 // Formatting code (indent the tree so it looks nice on the screen)
1381 var nopad = nopadtext;
1382 if (lastnode == 'SPAN') {
1386 if (currentElementChild.nodeName == '#text') {
1387 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1388 toadd = nopadtext ? toadd : toadd.trim();
1389 if (!nopad && toadd.length > 80) {
1390 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1395 currentElementChild = currentElement.childNodes.item(i);
1401 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1403 // Recursively traverse the tree structure of the child node
1404 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1405 lastnode = currentElementChild.nodeName;
1407 currentElementChild=currentElement.childNodes.item(i);
1413 // The remaining code is mostly for formatting the tree
1414 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1419 ret+= "</"+tagName+">";
1425 applyBlacklists : function()
1427 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1428 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1432 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1433 if (b.indexOf(tag) > -1) {
1436 this.white.push(tag);
1440 Roo.each(w, function(tag) {
1441 if (b.indexOf(tag) > -1) {
1444 if (this.white.indexOf(tag) > -1) {
1447 this.white.push(tag);
1452 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1453 if (w.indexOf(tag) > -1) {
1456 this.black.push(tag);
1460 Roo.each(b, function(tag) {
1461 if (w.indexOf(tag) > -1) {
1464 if (this.black.indexOf(tag) > -1) {
1467 this.black.push(tag);
1472 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1473 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1477 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1478 if (b.indexOf(tag) > -1) {
1481 this.cwhite.push(tag);
1485 Roo.each(w, function(tag) {
1486 if (b.indexOf(tag) > -1) {
1489 if (this.cwhite.indexOf(tag) > -1) {
1492 this.cwhite.push(tag);
1497 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1498 if (w.indexOf(tag) > -1) {
1501 this.cblack.push(tag);
1505 Roo.each(b, function(tag) {
1506 if (w.indexOf(tag) > -1) {
1509 if (this.cblack.indexOf(tag) > -1) {
1512 this.cblack.push(tag);
1517 setStylesheets : function(stylesheets)
1519 if(typeof(stylesheets) == 'string'){
1520 Roo.get(this.iframe.contentDocument.head).createChild({
1531 Roo.each(stylesheets, function(s) {
1536 Roo.get(_this.iframe.contentDocument.head).createChild({
1547 removeStylesheets : function()
1551 Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1556 // hide stuff that is not compatible
1574 * @cfg {String} fieldClass @hide
1577 * @cfg {String} focusClass @hide
1580 * @cfg {String} autoCreate @hide
1583 * @cfg {String} inputType @hide
1586 * @cfg {String} invalidClass @hide
1589 * @cfg {String} invalidText @hide
1592 * @cfg {String} msgFx @hide
1595 * @cfg {String} validateOnBlur @hide
1599 Roo.HtmlEditorCore.white = [
1600 'area', 'br', 'img', 'input', 'hr', 'wbr',
1602 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1603 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1604 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1605 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1606 'table', 'ul', 'xmp',
1608 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1611 'dir', 'menu', 'ol', 'ul', 'dl',
1617 Roo.HtmlEditorCore.black = [
1618 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1620 'base', 'basefont', 'bgsound', 'blink', 'body',
1621 'frame', 'frameset', 'head', 'html', 'ilayer',
1622 'iframe', 'layer', 'link', 'meta', 'object',
1623 'script', 'style' ,'title', 'xml' // clean later..
1625 Roo.HtmlEditorCore.clean = [
1626 'script', 'style', 'title', 'xml'
1628 Roo.HtmlEditorCore.remove = [
1633 Roo.HtmlEditorCore.ablack = [
1637 Roo.HtmlEditorCore.aclean = [
1638 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1642 Roo.HtmlEditorCore.pwhite= [
1643 'http', 'https', 'mailto'
1646 // white listed style attributes.
1647 Roo.HtmlEditorCore.cwhite= [
1648 // 'text-align', /// default is to allow most things..
1654 // black listed style attributes.
1655 Roo.HtmlEditorCore.cblack= [
1656 // 'font-size' -- this can be set by the project
1660 Roo.HtmlEditorCore.swapCodes =[