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){
516 this.owner.fireEvent('editorevent', this, e);
517 // this.updateToolbar();
518 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
521 insertTag : function(tg)
523 // could be a bit smarter... -> wrap the current selected tRoo..
524 if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
526 range = this.createRange(this.getSelection());
527 var wrappingNode = this.doc.createElement(tg.toLowerCase());
528 wrappingNode.appendChild(range.extractContents());
529 range.insertNode(wrappingNode);
536 this.execCmd("formatblock", tg);
540 insertText : function(txt)
544 var range = this.createRange();
545 range.deleteContents();
546 //alert(Sender.getAttribute('label'));
548 range.insertNode(this.doc.createTextNode(txt));
554 * Executes a Midas editor command on the editor document and performs necessary focus and
555 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
556 * @param {String} cmd The Midas command
557 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
559 relayCmd : function(cmd, value){
561 this.execCmd(cmd, value);
562 this.owner.fireEvent('editorevent', this);
563 //this.updateToolbar();
564 this.owner.deferFocus();
568 * Executes a Midas editor command directly on the editor document.
569 * For visual commands, you should use {@link #relayCmd} instead.
570 * <b>This should only be called after the editor is initialized.</b>
571 * @param {String} cmd The Midas command
572 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
574 execCmd : function(cmd, value){
575 this.doc.execCommand(cmd, false, value === undefined ? null : value);
582 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
584 * @param {String} text | dom node..
586 insertAtCursor : function(text)
597 var r = this.doc.selection.createRange();
608 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
612 // from jquery ui (MIT licenced)
616 if (win.getSelection && win.getSelection().getRangeAt) {
617 range = win.getSelection().getRangeAt(0);
618 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
619 range.insertNode(node);
620 } else if (win.document.selection && win.document.selection.createRange) {
621 // no firefox support
622 var txt = typeof(text) == 'string' ? text : text.outerHTML;
623 win.document.selection.createRange().pasteHTML(txt);
625 // no firefox support
626 var txt = typeof(text) == 'string' ? text : text.outerHTML;
627 this.execCmd('InsertHTML', txt);
636 mozKeyPress : function(e){
638 var c = e.getCharCode(), cmd;
641 c = String.fromCharCode(c).toLowerCase();
655 this.cleanUpPaste.defer(100, this);
671 fixKeys : function(){ // load time branching for fastest keydown performance
674 var k = e.getKey(), r;
677 r = this.doc.selection.createRange();
680 r.pasteHTML('    ');
687 r = this.doc.selection.createRange();
689 var target = r.parentElement();
690 if(!target || target.tagName.toLowerCase() != 'li'){
692 r.pasteHTML('<br />');
698 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
699 this.cleanUpPaste.defer(100, this);
705 }else if(Roo.isOpera){
711 this.execCmd('InsertHTML','    ');
714 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
715 this.cleanUpPaste.defer(100, this);
720 }else if(Roo.isSafari){
726 this.execCmd('InsertText','\t');
730 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
731 this.cleanUpPaste.defer(100, this);
739 getAllAncestors: function()
741 var p = this.getSelectedNode();
744 a.push(p); // push blank onto stack..
745 p = this.getParentElement();
749 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
753 a.push(this.doc.body);
760 getSelection : function()
763 return Roo.isIE ? this.doc.selection : this.win.getSelection();
766 getSelectedNode: function()
768 // this may only work on Gecko!!!
770 // should we cache this!!!!
775 var range = this.createRange(this.getSelection()).cloneRange();
778 var parent = range.parentElement();
780 var testRange = range.duplicate();
781 testRange.moveToElementText(parent);
782 if (testRange.inRange(range)) {
785 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
788 parent = parent.parentElement;
793 // is ancestor a text element.
794 var ac = range.commonAncestorContainer;
795 if (ac.nodeType == 3) {
799 var ar = ac.childNodes;
802 var other_nodes = [];
803 var has_other_nodes = false;
804 for (var i=0;i<ar.length;i++) {
805 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
808 // fullly contained node.
810 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
815 // probably selected..
816 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
817 other_nodes.push(ar[i]);
821 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
826 has_other_nodes = true;
828 if (!nodes.length && other_nodes.length) {
831 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
837 createRange: function(sel)
839 // this has strange effects when using with
840 // top toolbar - not sure if it's a great idea.
841 //this.editor.contentWindow.focus();
842 if (typeof sel != "undefined") {
844 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
846 return this.doc.createRange();
849 return this.doc.createRange();
852 getParentElement: function()
856 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
858 var range = this.createRange(sel);
861 var p = range.commonAncestorContainer;
862 while (p.nodeType == 3) { // text node
873 * Range intersection.. the hard stuff...
877 * [ -- selected range --- ]
881 * if end is before start or hits it. fail.
882 * if start is after end or hits it fail.
884 * if either hits (but other is outside. - then it's not
890 // @see http://www.thismuchiknow.co.uk/?p=64.
891 rangeIntersectsNode : function(range, node)
893 var nodeRange = node.ownerDocument.createRange();
895 nodeRange.selectNode(node);
897 nodeRange.selectNodeContents(node);
900 var rangeStartRange = range.cloneRange();
901 rangeStartRange.collapse(true);
903 var rangeEndRange = range.cloneRange();
904 rangeEndRange.collapse(false);
906 var nodeStartRange = nodeRange.cloneRange();
907 nodeStartRange.collapse(true);
909 var nodeEndRange = nodeRange.cloneRange();
910 nodeEndRange.collapse(false);
912 return rangeStartRange.compareBoundaryPoints(
913 Range.START_TO_START, nodeEndRange) == -1 &&
914 rangeEndRange.compareBoundaryPoints(
915 Range.START_TO_START, nodeStartRange) == 1;
919 rangeCompareNode : function(range, node)
921 var nodeRange = node.ownerDocument.createRange();
923 nodeRange.selectNode(node);
925 nodeRange.selectNodeContents(node);
929 range.collapse(true);
931 nodeRange.collapse(true);
933 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
934 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
936 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
938 var nodeIsBefore = ss == 1;
939 var nodeIsAfter = ee == -1;
941 if (nodeIsBefore && nodeIsAfter)
943 if (!nodeIsBefore && nodeIsAfter)
944 return 1; //right trailed.
946 if (nodeIsBefore && !nodeIsAfter)
947 return 2; // left trailed.
952 // private? - in a new class?
953 cleanUpPaste : function()
955 // cleans up the whole document..
956 Roo.log('cleanuppaste');
958 this.cleanUpChildren(this.doc.body);
959 var clean = this.cleanWordChars(this.doc.body.innerHTML);
960 if (clean != this.doc.body.innerHTML) {
961 this.doc.body.innerHTML = clean;
966 cleanWordChars : function(input) {// change the chars to hex code
967 var he = Roo.HtmlEditorCore;
970 Roo.each(he.swapCodes, function(sw) {
971 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
973 output = output.replace(swapper, sw[1]);
980 cleanUpChildren : function (n)
982 if (!n.childNodes.length) {
985 for (var i = n.childNodes.length-1; i > -1 ; i--) {
986 this.cleanUpChild(n.childNodes[i]);
993 cleanUpChild : function (node)
997 if (node.nodeName == "#text") {
998 // clean up silly Windows -- stuff?
1001 if (node.nodeName == "#comment") {
1002 node.parentNode.removeChild(node);
1003 // clean up silly Windows -- stuff?
1006 var lcname = node.tagName.toLowerCase();
1007 // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full
1008 // whitelist of tags..
1010 if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
1012 node.parentNode.removeChild(node);
1017 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1019 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1020 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1022 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1023 // remove_keep_children = true;
1026 if (remove_keep_children) {
1027 this.cleanUpChildren(node);
1028 // inserts everything just before this node...
1029 while (node.childNodes.length) {
1030 var cn = node.childNodes[0];
1031 node.removeChild(cn);
1032 node.parentNode.insertBefore(cn, node);
1034 node.parentNode.removeChild(node);
1038 if (!node.attributes || !node.attributes.length) {
1039 this.cleanUpChildren(node);
1043 function cleanAttr(n,v)
1046 if (v.match(/^\./) || v.match(/^\//)) {
1049 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1052 if (v.match(/^#/)) {
1055 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1056 node.removeAttribute(n);
1060 var cwhite = this.cwhite;
1061 var cblack = this.cblack;
1063 function cleanStyle(n,v)
1065 if (v.match(/expression/)) { //XSS?? should we even bother..
1066 node.removeAttribute(n);
1070 var parts = v.split(/;/);
1073 Roo.each(parts, function(p) {
1074 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1078 var l = p.split(':').shift().replace(/\s+/g,'');
1079 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1081 if ( cwhite.length && cblack.indexOf(l) > -1) {
1082 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1083 //node.removeAttribute(n);
1087 // only allow 'c whitelisted system attributes'
1088 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1089 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1090 //node.removeAttribute(n);
1101 node.setAttribute(n, clean.join(';'));
1103 node.removeAttribute(n);
1109 for (var i = node.attributes.length-1; i > -1 ; i--) {
1110 var a = node.attributes[i];
1113 if (a.name.toLowerCase().substr(0,2)=='on') {
1114 node.removeAttribute(a.name);
1117 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1118 node.removeAttribute(a.name);
1121 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1122 cleanAttr(a.name,a.value); // fixme..
1125 if (a.name == 'style') {
1126 cleanStyle(a.name,a.value);
1129 /// clean up MS crap..
1130 // tecnically this should be a list of valid class'es..
1133 if (a.name == 'class') {
1134 if (a.value.match(/^Mso/)) {
1135 node.className = '';
1138 if (a.value.match(/body/)) {
1139 node.className = '';
1150 this.cleanUpChildren(node);
1155 * Clean up MS wordisms...
1157 cleanWord : function(node)
1160 var cleanWordChildren = function()
1162 if (!node.childNodes.length) {
1165 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1166 _t.cleanWord(node.childNodes[i]);
1172 this.cleanWord(this.doc.body);
1175 if (node.nodeName == "#text") {
1176 // clean up silly Windows -- stuff?
1179 if (node.nodeName == "#comment") {
1180 node.parentNode.removeChild(node);
1181 // clean up silly Windows -- stuff?
1185 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1186 node.parentNode.removeChild(node);
1190 // remove - but keep children..
1191 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1192 while (node.childNodes.length) {
1193 var cn = node.childNodes[0];
1194 node.removeChild(cn);
1195 node.parentNode.insertBefore(cn, node);
1197 node.parentNode.removeChild(node);
1198 cleanWordChildren();
1202 if (node.className.length) {
1204 var cn = node.className.split(/\W+/);
1206 Roo.each(cn, function(cls) {
1207 if (cls.match(/Mso[a-zA-Z]+/)) {
1212 node.className = cna.length ? cna.join(' ') : '';
1214 node.removeAttribute("class");
1218 if (node.hasAttribute("lang")) {
1219 node.removeAttribute("lang");
1222 if (node.hasAttribute("style")) {
1224 var styles = node.getAttribute("style").split(";");
1226 Roo.each(styles, function(s) {
1227 if (!s.match(/:/)) {
1230 var kv = s.split(":");
1231 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1234 // what ever is left... we allow.
1237 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1238 if (!nstyle.length) {
1239 node.removeAttribute('style');
1243 cleanWordChildren();
1247 domToHTML : function(currentElement, depth, nopadtext) {
1250 nopadtext = nopadtext || false;
1252 if (!currentElement) {
1253 return this.domToHTML(this.doc.body);
1256 //Roo.log(currentElement);
1258 var allText = false;
1259 var nodeName = currentElement.nodeName;
1260 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1262 if (nodeName == '#text') {
1264 return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim();
1269 if (nodeName != 'BODY') {
1272 // Prints the node tagName, such as <A>, <IMG>, etc
1275 for(i = 0; i < currentElement.attributes.length;i++) {
1277 var aname = currentElement.attributes.item(i).name;
1278 if (!currentElement.attributes.item(i).value.length) {
1281 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1284 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1293 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1296 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1301 // Traverse the tree
1303 var currentElementChild = currentElement.childNodes.item(i);
1307 while (currentElementChild) {
1308 // Formatting code (indent the tree so it looks nice on the screen)
1309 var nopad = nopadtext;
1310 if (lastnode == 'SPAN') {
1314 if (currentElementChild.nodeName == '#text') {
1315 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1316 toadd = nopadtext ? toadd : toadd.trim();
1317 if (!nopad && toadd.length > 80) {
1318 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1323 currentElementChild = currentElement.childNodes.item(i);
1329 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1331 // Recursively traverse the tree structure of the child node
1332 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1333 lastnode = currentElementChild.nodeName;
1335 currentElementChild=currentElement.childNodes.item(i);
1341 // The remaining code is mostly for formatting the tree
1342 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1347 ret+= "</"+tagName+">";
1353 applyBlacklists : function()
1355 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1356 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1360 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1361 if (b.indexOf(tag) > -1) {
1364 this.white.push(tag);
1368 Roo.each(w, function(tag) {
1369 if (b.indexOf(tag) > -1) {
1372 if (this.white.indexOf(tag) > -1) {
1375 this.white.push(tag);
1380 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1381 if (w.indexOf(tag) > -1) {
1384 this.black.push(tag);
1388 Roo.each(b, function(tag) {
1389 if (w.indexOf(tag) > -1) {
1392 if (this.black.indexOf(tag) > -1) {
1395 this.black.push(tag);
1400 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1401 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1405 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1406 if (b.indexOf(tag) > -1) {
1409 this.cwhite.push(tag);
1413 Roo.each(w, function(tag) {
1414 if (b.indexOf(tag) > -1) {
1417 if (this.cwhite.indexOf(tag) > -1) {
1420 this.cwhite.push(tag);
1425 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1426 if (w.indexOf(tag) > -1) {
1429 this.cblack.push(tag);
1433 Roo.each(b, function(tag) {
1434 if (w.indexOf(tag) > -1) {
1437 if (this.cblack.indexOf(tag) > -1) {
1440 this.cblack.push(tag);
1445 setStylesheets : function(stylesheets)
1447 Roo.log('setting stylesheets!!!!!!!!!!!!!!!!!!!!!!!!!!');
1448 if(typeof(stylesheets) == 'string'){
1449 Roo.get(this.iframe.contentDocument.head).createChild({
1460 Roo.each(stylesheets, function(s) {
1465 Roo.get(_this.iframe.contentDocument.head).createChild({
1476 removeStylesheets : function()
1480 Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1485 // hide stuff that is not compatible
1503 * @cfg {String} fieldClass @hide
1506 * @cfg {String} focusClass @hide
1509 * @cfg {String} autoCreate @hide
1512 * @cfg {String} inputType @hide
1515 * @cfg {String} invalidClass @hide
1518 * @cfg {String} invalidText @hide
1521 * @cfg {String} msgFx @hide
1524 * @cfg {String} validateOnBlur @hide
1528 Roo.HtmlEditorCore.white = [
1529 'area', 'br', 'img', 'input', 'hr', 'wbr',
1531 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1532 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1533 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1534 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1535 'table', 'ul', 'xmp',
1537 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1540 'dir', 'menu', 'ol', 'ul', 'dl',
1546 Roo.HtmlEditorCore.black = [
1547 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1549 'base', 'basefont', 'bgsound', 'blink', 'body',
1550 'frame', 'frameset', 'head', 'html', 'ilayer',
1551 'iframe', 'layer', 'link', 'meta', 'object',
1552 'script', 'style' ,'title', 'xml' // clean later..
1554 Roo.HtmlEditorCore.clean = [
1555 'script', 'style', 'title', 'xml'
1557 Roo.HtmlEditorCore.remove = [
1562 Roo.HtmlEditorCore.ablack = [
1566 Roo.HtmlEditorCore.aclean = [
1567 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1571 Roo.HtmlEditorCore.pwhite= [
1572 'http', 'https', 'mailto'
1575 // white listed style attributes.
1576 Roo.HtmlEditorCore.cwhite= [
1577 // 'text-align', /// default is to allow most things..
1583 // black listed style attributes.
1584 Roo.HtmlEditorCore.cblack= [
1585 // 'font-size' -- this can be set by the project
1589 Roo.HtmlEditorCore.swapCodes =[