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 textNode = node.firstChild;
1181 node.removeChild(textNode);
1183 node.parentNode.insertBefore(node.ownerDocument.createTextNode(" "), node);
1184 node.parentNode.insertBefore(textNode, node);
1185 node.parentNode.insertBefore(node.ownerDocument.createTextNode(" ") , node);
1186 node.parentNode.removeChild(node);
1189 if (node.nodeName == "#text") {
1190 // clean up silly Windows -- stuff?
1193 if (node.nodeName == "#comment") {
1194 node.parentNode.removeChild(node);
1195 // clean up silly Windows -- stuff?
1199 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1200 node.parentNode.removeChild(node);
1204 // remove - but keep children..
1205 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1206 while (node.childNodes.length) {
1207 var cn = node.childNodes[0];
1208 node.removeChild(cn);
1209 node.parentNode.insertBefore(cn, node);
1211 node.parentNode.removeChild(node);
1212 this.iterateChildren(node, this.cleanWord);
1216 if (node.className.length) {
1218 var cn = node.className.split(/\W+/);
1220 Roo.each(cn, function(cls) {
1221 if (cls.match(/Mso[a-zA-Z]+/)) {
1226 node.className = cna.length ? cna.join(' ') : '';
1228 node.removeAttribute("class");
1232 if (node.hasAttribute("lang")) {
1233 node.removeAttribute("lang");
1236 if (node.hasAttribute("style")) {
1238 var styles = node.getAttribute("style").split(";");
1240 Roo.each(styles, function(s) {
1241 if (!s.match(/:/)) {
1244 var kv = s.split(":");
1245 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1248 // what ever is left... we allow.
1251 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1252 if (!nstyle.length) {
1253 node.removeAttribute('style');
1256 this.iterateChildren(node, this.cleanWord);
1262 * iterateChildren of a Node, calling fn each time, using this as the scole..
1263 * @param {DomNode} node node to iterate children of.
1264 * @param {Function} fn method of this class to call on each item.
1266 iterateChildren : function(node, fn)
1268 if (!node.childNodes.length) {
1271 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1272 fn.call(this, node.childNodes[i])
1280 * Quite often pasting from word etc.. results in tables with column and widths.
1281 * This does not work well on fluid HTML layouts - like emails. - so this code should hunt an destroy them..
1284 cleanTableWidths : function(node)
1289 this.cleanTableWidths(this.doc.body);
1294 if (node.nodeName == "#text" || node.nodeName == "#comment") {
1297 Roo.log(node.tagName);
1298 if (!node.tagName.toLowerCase().match(/^(table|td|tr)$/)) {
1299 this.iterateChildren(node, this.cleanTableWidths);
1302 if (node.hasAttribute('width')) {
1303 node.removeAttribute('width');
1307 if (node.hasAttribute("style")) {
1310 var styles = node.getAttribute("style").split(";");
1312 Roo.each(styles, function(s) {
1313 if (!s.match(/:/)) {
1316 var kv = s.split(":");
1317 if (kv[0].match(/^\s*(width|min-width)\s*$/)) {
1320 // what ever is left... we allow.
1323 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1324 if (!nstyle.length) {
1325 node.removeAttribute('style');
1329 this.iterateChildren(node, this.cleanTableWidths);
1337 domToHTML : function(currentElement, depth, nopadtext) {
1340 nopadtext = nopadtext || false;
1342 if (!currentElement) {
1343 return this.domToHTML(this.doc.body);
1346 //Roo.log(currentElement);
1348 var allText = false;
1349 var nodeName = currentElement.nodeName;
1350 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1352 if (nodeName == '#text') {
1354 return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim();
1359 if (nodeName != 'BODY') {
1362 // Prints the node tagName, such as <A>, <IMG>, etc
1365 for(i = 0; i < currentElement.attributes.length;i++) {
1367 var aname = currentElement.attributes.item(i).name;
1368 if (!currentElement.attributes.item(i).value.length) {
1371 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1374 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1383 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1386 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1391 // Traverse the tree
1393 var currentElementChild = currentElement.childNodes.item(i);
1397 while (currentElementChild) {
1398 // Formatting code (indent the tree so it looks nice on the screen)
1399 var nopad = nopadtext;
1400 if (lastnode == 'SPAN') {
1404 if (currentElementChild.nodeName == '#text') {
1405 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1406 toadd = nopadtext ? toadd : toadd.trim();
1407 if (!nopad && toadd.length > 80) {
1408 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1413 currentElementChild = currentElement.childNodes.item(i);
1419 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1421 // Recursively traverse the tree structure of the child node
1422 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1423 lastnode = currentElementChild.nodeName;
1425 currentElementChild=currentElement.childNodes.item(i);
1431 // The remaining code is mostly for formatting the tree
1432 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1437 ret+= "</"+tagName+">";
1443 applyBlacklists : function()
1445 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1446 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1450 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1451 if (b.indexOf(tag) > -1) {
1454 this.white.push(tag);
1458 Roo.each(w, function(tag) {
1459 if (b.indexOf(tag) > -1) {
1462 if (this.white.indexOf(tag) > -1) {
1465 this.white.push(tag);
1470 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1471 if (w.indexOf(tag) > -1) {
1474 this.black.push(tag);
1478 Roo.each(b, function(tag) {
1479 if (w.indexOf(tag) > -1) {
1482 if (this.black.indexOf(tag) > -1) {
1485 this.black.push(tag);
1490 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1491 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1495 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1496 if (b.indexOf(tag) > -1) {
1499 this.cwhite.push(tag);
1503 Roo.each(w, function(tag) {
1504 if (b.indexOf(tag) > -1) {
1507 if (this.cwhite.indexOf(tag) > -1) {
1510 this.cwhite.push(tag);
1515 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1516 if (w.indexOf(tag) > -1) {
1519 this.cblack.push(tag);
1523 Roo.each(b, function(tag) {
1524 if (w.indexOf(tag) > -1) {
1527 if (this.cblack.indexOf(tag) > -1) {
1530 this.cblack.push(tag);
1535 setStylesheets : function(stylesheets)
1537 if(typeof(stylesheets) == 'string'){
1538 Roo.get(this.iframe.contentDocument.head).createChild({
1549 Roo.each(stylesheets, function(s) {
1554 Roo.get(_this.iframe.contentDocument.head).createChild({
1565 removeStylesheets : function()
1569 Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1574 setStyle : function(style)
1576 Roo.get(this.iframe.contentDocument.head).createChild({
1585 // hide stuff that is not compatible
1603 * @cfg {String} fieldClass @hide
1606 * @cfg {String} focusClass @hide
1609 * @cfg {String} autoCreate @hide
1612 * @cfg {String} inputType @hide
1615 * @cfg {String} invalidClass @hide
1618 * @cfg {String} invalidText @hide
1621 * @cfg {String} msgFx @hide
1624 * @cfg {String} validateOnBlur @hide
1628 Roo.HtmlEditorCore.white = [
1629 'area', 'br', 'img', 'input', 'hr', 'wbr',
1631 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1632 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1633 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1634 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1635 'table', 'ul', 'xmp',
1637 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1640 'dir', 'menu', 'ol', 'ul', 'dl',
1646 Roo.HtmlEditorCore.black = [
1647 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1649 'base', 'basefont', 'bgsound', 'blink', 'body',
1650 'frame', 'frameset', 'head', 'html', 'ilayer',
1651 'iframe', 'layer', 'link', 'meta', 'object',
1652 'script', 'style' ,'title', 'xml' // clean later..
1654 Roo.HtmlEditorCore.clean = [
1655 'script', 'style', 'title', 'xml'
1657 Roo.HtmlEditorCore.remove = [
1662 Roo.HtmlEditorCore.ablack = [
1666 Roo.HtmlEditorCore.aclean = [
1667 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1671 Roo.HtmlEditorCore.pwhite= [
1672 'http', 'https', 'mailto'
1675 // white listed style attributes.
1676 Roo.HtmlEditorCore.cwhite= [
1677 // 'text-align', /// default is to allow most things..
1683 // black listed style attributes.
1684 Roo.HtmlEditorCore.cblack= [
1685 // 'font-size' -- this can be set by the project
1689 Roo.HtmlEditorCore.swapCodes =[