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)
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)
944 if (!nodeIsBefore && nodeIsAfter)
945 return 1; //right trailed.
947 if (nodeIsBefore && !nodeIsAfter)
948 return 2; // left trailed.
953 // private? - in a new class?
954 cleanUpPaste : function()
956 // cleans up the whole document..
957 Roo.log('cleanuppaste');
959 this.cleanUpChildren(this.doc.body);
960 var clean = this.cleanWordChars(this.doc.body.innerHTML);
961 if (clean != this.doc.body.innerHTML) {
962 this.doc.body.innerHTML = clean;
967 cleanWordChars : function(input) {// change the chars to hex code
968 var he = Roo.HtmlEditorCore;
971 Roo.each(he.swapCodes, function(sw) {
972 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
974 output = output.replace(swapper, sw[1]);
981 cleanUpChildren : function (n)
983 if (!n.childNodes.length) {
986 for (var i = n.childNodes.length-1; i > -1 ; i--) {
987 this.cleanUpChild(n.childNodes[i]);
994 cleanUpChild : function (node)
998 if (node.nodeName == "#text") {
999 // clean up silly Windows -- stuff?
1002 if (node.nodeName == "#comment") {
1003 node.parentNode.removeChild(node);
1004 // clean up silly Windows -- stuff?
1007 var lcname = node.tagName.toLowerCase();
1008 // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full
1009 // whitelist of tags..
1011 if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
1013 node.parentNode.removeChild(node);
1018 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1020 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1021 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1023 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1024 // remove_keep_children = true;
1027 if (remove_keep_children) {
1028 this.cleanUpChildren(node);
1029 // inserts everything just before this node...
1030 while (node.childNodes.length) {
1031 var cn = node.childNodes[0];
1032 node.removeChild(cn);
1033 node.parentNode.insertBefore(cn, node);
1035 node.parentNode.removeChild(node);
1039 if (!node.attributes || !node.attributes.length) {
1040 this.cleanUpChildren(node);
1044 function cleanAttr(n,v)
1047 if (v.match(/^\./) || v.match(/^\//)) {
1050 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1053 if (v.match(/^#/)) {
1056 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1057 node.removeAttribute(n);
1061 var cwhite = this.cwhite;
1062 var cblack = this.cblack;
1064 function cleanStyle(n,v)
1066 if (v.match(/expression/)) { //XSS?? should we even bother..
1067 node.removeAttribute(n);
1071 var parts = v.split(/;/);
1074 Roo.each(parts, function(p) {
1075 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1079 var l = p.split(':').shift().replace(/\s+/g,'');
1080 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1082 if ( cwhite.length && cblack.indexOf(l) > -1) {
1083 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1084 //node.removeAttribute(n);
1088 // only allow 'c whitelisted system attributes'
1089 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1090 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1091 //node.removeAttribute(n);
1102 node.setAttribute(n, clean.join(';'));
1104 node.removeAttribute(n);
1110 for (var i = node.attributes.length-1; i > -1 ; i--) {
1111 var a = node.attributes[i];
1114 if (a.name.toLowerCase().substr(0,2)=='on') {
1115 node.removeAttribute(a.name);
1118 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1119 node.removeAttribute(a.name);
1122 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1123 cleanAttr(a.name,a.value); // fixme..
1126 if (a.name == 'style') {
1127 cleanStyle(a.name,a.value);
1130 /// clean up MS crap..
1131 // tecnically this should be a list of valid class'es..
1134 if (a.name == 'class') {
1135 if (a.value.match(/^Mso/)) {
1136 node.className = '';
1139 if (a.value.match(/body/)) {
1140 node.className = '';
1151 this.cleanUpChildren(node);
1157 * Clean up MS wordisms...
1159 cleanWord : function(node)
1162 var cleanWordChildren = function()
1164 if (!node.childNodes.length) {
1167 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1168 _t.cleanWord(node.childNodes[i]);
1174 this.cleanWord(this.doc.body);
1177 if (node.nodeName == "#text") {
1178 // clean up silly Windows -- stuff?
1181 if (node.nodeName == "#comment") {
1182 node.parentNode.removeChild(node);
1183 // clean up silly Windows -- stuff?
1187 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1188 node.parentNode.removeChild(node);
1192 // remove - but keep children..
1193 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1194 while (node.childNodes.length) {
1195 var cn = node.childNodes[0];
1196 node.removeChild(cn);
1197 node.parentNode.insertBefore(cn, node);
1199 node.parentNode.removeChild(node);
1200 cleanWordChildren();
1204 if (node.className.length) {
1206 var cn = node.className.split(/\W+/);
1208 Roo.each(cn, function(cls) {
1209 if (cls.match(/Mso[a-zA-Z]+/)) {
1214 node.className = cna.length ? cna.join(' ') : '';
1216 node.removeAttribute("class");
1220 if (node.hasAttribute("lang")) {
1221 node.removeAttribute("lang");
1224 if (node.hasAttribute("style")) {
1226 var styles = node.getAttribute("style").split(";");
1228 Roo.each(styles, function(s) {
1229 if (!s.match(/:/)) {
1232 var kv = s.split(":");
1233 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1236 // what ever is left... we allow.
1239 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1240 if (!nstyle.length) {
1241 node.removeAttribute('style');
1245 cleanWordChildren();
1250 iterateChildren : function(node, fn)
1252 if (!node.childNodes.length) {
1255 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1256 fn.call(this.node.childNodes[i])
1264 * Quite often pasting from word etc.. results in tables with column and widths.
1265 * This does not work well on fluid HTML layouts - like emails. - so this code should hunt an destroy them..
1268 cleanTableWidths : function(node)
1271 var cleanTableWidthsChildren = function()
1273 if (!node.childNodes.length) {
1276 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1277 _t.cleanTableWidths(node.childNodes[i]);
1285 this.cleanWord(this.doc.body);
1288 if (node.nodeName == "#text") {
1289 // clean up silly Windows -- stuff?
1292 if (node.nodeName == "#comment") {
1293 node.parentNode.removeChild(node);
1294 // clean up silly Windows -- stuff?
1298 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1299 node.parentNode.removeChild(node);
1303 // remove - but keep children..
1304 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1305 while (node.childNodes.length) {
1306 var cn = node.childNodes[0];
1307 node.removeChild(cn);
1308 node.parentNode.insertBefore(cn, node);
1310 node.parentNode.removeChild(node);
1311 cleanWordChildren();
1315 if (node.className.length) {
1317 var cn = node.className.split(/\W+/);
1319 Roo.each(cn, function(cls) {
1320 if (cls.match(/Mso[a-zA-Z]+/)) {
1325 node.className = cna.length ? cna.join(' ') : '';
1327 node.removeAttribute("class");
1331 if (node.hasAttribute("lang")) {
1332 node.removeAttribute("lang");
1335 if (node.hasAttribute("style")) {
1337 var styles = node.getAttribute("style").split(";");
1339 Roo.each(styles, function(s) {
1340 if (!s.match(/:/)) {
1343 var kv = s.split(":");
1344 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1347 // what ever is left... we allow.
1350 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1351 if (!nstyle.length) {
1352 node.removeAttribute('style');
1356 cleanWordChildren();
1364 domToHTML : function(currentElement, depth, nopadtext) {
1367 nopadtext = nopadtext || false;
1369 if (!currentElement) {
1370 return this.domToHTML(this.doc.body);
1373 //Roo.log(currentElement);
1375 var allText = false;
1376 var nodeName = currentElement.nodeName;
1377 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1379 if (nodeName == '#text') {
1381 return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim();
1386 if (nodeName != 'BODY') {
1389 // Prints the node tagName, such as <A>, <IMG>, etc
1392 for(i = 0; i < currentElement.attributes.length;i++) {
1394 var aname = currentElement.attributes.item(i).name;
1395 if (!currentElement.attributes.item(i).value.length) {
1398 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1401 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1410 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1413 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1418 // Traverse the tree
1420 var currentElementChild = currentElement.childNodes.item(i);
1424 while (currentElementChild) {
1425 // Formatting code (indent the tree so it looks nice on the screen)
1426 var nopad = nopadtext;
1427 if (lastnode == 'SPAN') {
1431 if (currentElementChild.nodeName == '#text') {
1432 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1433 toadd = nopadtext ? toadd : toadd.trim();
1434 if (!nopad && toadd.length > 80) {
1435 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1440 currentElementChild = currentElement.childNodes.item(i);
1446 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1448 // Recursively traverse the tree structure of the child node
1449 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1450 lastnode = currentElementChild.nodeName;
1452 currentElementChild=currentElement.childNodes.item(i);
1458 // The remaining code is mostly for formatting the tree
1459 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1464 ret+= "</"+tagName+">";
1470 applyBlacklists : function()
1472 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1473 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1477 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1478 if (b.indexOf(tag) > -1) {
1481 this.white.push(tag);
1485 Roo.each(w, function(tag) {
1486 if (b.indexOf(tag) > -1) {
1489 if (this.white.indexOf(tag) > -1) {
1492 this.white.push(tag);
1497 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1498 if (w.indexOf(tag) > -1) {
1501 this.black.push(tag);
1505 Roo.each(b, function(tag) {
1506 if (w.indexOf(tag) > -1) {
1509 if (this.black.indexOf(tag) > -1) {
1512 this.black.push(tag);
1517 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1518 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1522 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1523 if (b.indexOf(tag) > -1) {
1526 this.cwhite.push(tag);
1530 Roo.each(w, function(tag) {
1531 if (b.indexOf(tag) > -1) {
1534 if (this.cwhite.indexOf(tag) > -1) {
1537 this.cwhite.push(tag);
1542 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1543 if (w.indexOf(tag) > -1) {
1546 this.cblack.push(tag);
1550 Roo.each(b, function(tag) {
1551 if (w.indexOf(tag) > -1) {
1554 if (this.cblack.indexOf(tag) > -1) {
1557 this.cblack.push(tag);
1562 setStylesheets : function(stylesheets)
1564 if(typeof(stylesheets) == 'string'){
1565 Roo.get(this.iframe.contentDocument.head).createChild({
1576 Roo.each(stylesheets, function(s) {
1581 Roo.get(_this.iframe.contentDocument.head).createChild({
1592 removeStylesheets : function()
1596 Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1601 // hide stuff that is not compatible
1619 * @cfg {String} fieldClass @hide
1622 * @cfg {String} focusClass @hide
1625 * @cfg {String} autoCreate @hide
1628 * @cfg {String} inputType @hide
1631 * @cfg {String} invalidClass @hide
1634 * @cfg {String} invalidText @hide
1637 * @cfg {String} msgFx @hide
1640 * @cfg {String} validateOnBlur @hide
1644 Roo.HtmlEditorCore.white = [
1645 'area', 'br', 'img', 'input', 'hr', 'wbr',
1647 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1648 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1649 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1650 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1651 'table', 'ul', 'xmp',
1653 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1656 'dir', 'menu', 'ol', 'ul', 'dl',
1662 Roo.HtmlEditorCore.black = [
1663 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1665 'base', 'basefont', 'bgsound', 'blink', 'body',
1666 'frame', 'frameset', 'head', 'html', 'ilayer',
1667 'iframe', 'layer', 'link', 'meta', 'object',
1668 'script', 'style' ,'title', 'xml' // clean later..
1670 Roo.HtmlEditorCore.clean = [
1671 'script', 'style', 'title', 'xml'
1673 Roo.HtmlEditorCore.remove = [
1678 Roo.HtmlEditorCore.ablack = [
1682 Roo.HtmlEditorCore.aclean = [
1683 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1687 Roo.HtmlEditorCore.pwhite= [
1688 'http', 'https', 'mailto'
1691 // white listed style attributes.
1692 Roo.HtmlEditorCore.cwhite= [
1693 // 'text-align', /// default is to allow most things..
1699 // black listed style attributes.
1700 Roo.HtmlEditorCore.cblack= [
1701 // 'font-size' -- this can be set by the project
1705 Roo.HtmlEditorCore.swapCodes =[