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);
244 if(this.stylesheets){
245 this.setStylesheets(this.stylesheets);
251 onResize : function(w, h)
253 Roo.log('resize: ' +w + ',' + h );
254 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
258 if(typeof w == 'number'){
260 this.iframe.style.width = w + 'px';
262 if(typeof h == 'number'){
264 this.iframe.style.height = h + 'px';
266 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
273 * Toggles the editor between standard and source edit mode.
274 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
276 toggleSourceEdit : function(sourceEditMode){
278 this.sourceEditMode = sourceEditMode === true;
280 if(this.sourceEditMode){
282 Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
285 Roo.get(this.iframe).removeClass(['x-hidden','hide']);
286 //this.iframe.className = '';
289 //this.setSize(this.owner.wrap.getSize());
290 //this.fireEvent('editmodechange', this, this.sourceEditMode);
297 * Protected method that will not generally be called directly. If you need/want
298 * custom HTML cleanup, this is the method you should override.
299 * @param {String} html The HTML to be cleaned
300 * return {String} The cleaned HTML
302 cleanHtml : function(html){
305 if(Roo.isSafari){ // strip safari nonsense
306 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
309 if(html == ' '){
316 * HTML Editor -> Textarea
317 * Protected method that will not generally be called directly. Syncs the contents
318 * of the editor iframe with the textarea.
320 syncValue : function(){
321 if(this.initialized){
322 var bd = (this.doc.body || this.doc.documentElement);
323 //this.cleanUpPaste(); -- this is done else where and causes havoc..
324 var html = bd.innerHTML;
326 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
327 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
329 html = '<div style="'+m[0]+'">' + html + '</div>';
332 html = this.cleanHtml(html);
333 // fix up the special chars.. normaly like back quotes in word...
334 // however we do not want to do this with chinese..
335 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
336 var cc = b.charCodeAt();
338 (cc >= 0x4E00 && cc < 0xA000 ) ||
339 (cc >= 0x3400 && cc < 0x4E00 ) ||
340 (cc >= 0xf900 && cc < 0xfb00 )
346 if(this.owner.fireEvent('beforesync', this, html) !== false){
347 this.el.dom.value = html;
348 this.owner.fireEvent('sync', this, html);
354 * Protected method that will not generally be called directly. Pushes the value of the textarea
355 * into the iframe editor.
357 pushValue : function(){
358 if(this.initialized){
359 var v = this.el.dom.value.trim();
365 if(this.owner.fireEvent('beforepush', this, v) !== false){
366 var d = (this.doc.body || this.doc.documentElement);
369 this.el.dom.value = d.innerHTML;
370 this.owner.fireEvent('push', this, v);
376 deferFocus : function(){
377 this.focus.defer(10, this);
382 if(this.win && !this.sourceEditMode){
389 assignDocWin: function()
391 var iframe = this.iframe;
394 this.doc = iframe.contentWindow.document;
395 this.win = iframe.contentWindow;
397 // if (!Roo.get(this.frameId)) {
400 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
401 // this.win = Roo.get(this.frameId).dom.contentWindow;
403 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
407 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
408 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
413 initEditor : function(){
414 //console.log("INIT EDITOR");
419 this.doc.designMode="on";
421 this.doc.write(this.getDocMarkup());
424 var dbody = (this.doc.body || this.doc.documentElement);
425 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
426 // this copies styles from the containing element into thsi one..
427 // not sure why we need all of this..
428 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
430 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
431 //ss['background-attachment'] = 'fixed'; // w3c
432 dbody.bgProperties = 'fixed'; // ie
433 //Roo.DomHelper.applyStyles(dbody, ss);
434 Roo.EventManager.on(this.doc, {
435 //'mousedown': this.onEditorEvent,
436 'mouseup': this.onEditorEvent,
437 'dblclick': this.onEditorEvent,
438 'click': this.onEditorEvent,
439 'keyup': this.onEditorEvent,
444 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
446 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
447 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
449 this.initialized = true;
451 this.owner.fireEvent('initialize', this);
456 onDestroy : function(){
462 //for (var i =0; i < this.toolbars.length;i++) {
463 // // fixme - ask toolbars for heights?
464 // this.toolbars[i].onDestroy();
467 //this.wrap.dom.innerHTML = '';
468 //this.wrap.remove();
473 onFirstFocus : function(){
478 this.activated = true;
481 if(Roo.isGecko){ // prevent silly gecko errors
483 var s = this.win.getSelection();
484 if(!s.focusNode || s.focusNode.nodeType != 3){
485 var r = s.getRangeAt(0);
486 r.selectNodeContents((this.doc.body || this.doc.documentElement));
491 this.execCmd('useCSS', true);
492 this.execCmd('styleWithCSS', false);
495 this.owner.fireEvent('activate', this);
499 adjustFont: function(btn){
500 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
501 //if(Roo.isSafari){ // safari
504 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
505 if(Roo.isSafari){ // safari
506 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
507 v = (v < 10) ? 10 : v;
508 v = (v > 48) ? 48 : v;
509 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
514 v = Math.max(1, v+adjust);
516 this.execCmd('FontSize', v );
519 onEditorEvent : function(e){
520 this.owner.fireEvent('editorevent', this, e);
521 // this.updateToolbar();
522 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
525 insertTag : function(tg)
527 // could be a bit smarter... -> wrap the current selected tRoo..
528 if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
530 range = this.createRange(this.getSelection());
531 var wrappingNode = this.doc.createElement(tg.toLowerCase());
532 wrappingNode.appendChild(range.extractContents());
533 range.insertNode(wrappingNode);
540 this.execCmd("formatblock", tg);
544 insertText : function(txt)
548 var range = this.createRange();
549 range.deleteContents();
550 //alert(Sender.getAttribute('label'));
552 range.insertNode(this.doc.createTextNode(txt));
558 * Executes a Midas editor command on the editor document and performs necessary focus and
559 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
560 * @param {String} cmd The Midas command
561 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
563 relayCmd : function(cmd, value){
565 this.execCmd(cmd, value);
566 this.owner.fireEvent('editorevent', this);
567 //this.updateToolbar();
568 this.owner.deferFocus();
572 * Executes a Midas editor command directly on the editor document.
573 * For visual commands, you should use {@link #relayCmd} instead.
574 * <b>This should only be called after the editor is initialized.</b>
575 * @param {String} cmd The Midas command
576 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
578 execCmd : function(cmd, value){
579 this.doc.execCommand(cmd, false, value === undefined ? null : value);
586 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
588 * @param {String} text | dom node..
590 insertAtCursor : function(text)
601 var r = this.doc.selection.createRange();
612 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
616 // from jquery ui (MIT licenced)
620 if (win.getSelection && win.getSelection().getRangeAt) {
621 range = win.getSelection().getRangeAt(0);
622 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
623 range.insertNode(node);
624 } else if (win.document.selection && win.document.selection.createRange) {
625 // no firefox support
626 var txt = typeof(text) == 'string' ? text : text.outerHTML;
627 win.document.selection.createRange().pasteHTML(txt);
629 // no firefox support
630 var txt = typeof(text) == 'string' ? text : text.outerHTML;
631 this.execCmd('InsertHTML', txt);
640 mozKeyPress : function(e){
642 var c = e.getCharCode(), cmd;
645 c = String.fromCharCode(c).toLowerCase();
659 this.cleanUpPaste.defer(100, this);
675 fixKeys : function(){ // load time branching for fastest keydown performance
678 var k = e.getKey(), r;
681 r = this.doc.selection.createRange();
684 r.pasteHTML('    ');
691 r = this.doc.selection.createRange();
693 var target = r.parentElement();
694 if(!target || target.tagName.toLowerCase() != 'li'){
696 r.pasteHTML('<br />');
702 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
703 this.cleanUpPaste.defer(100, this);
709 }else if(Roo.isOpera){
715 this.execCmd('InsertHTML','    ');
718 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
719 this.cleanUpPaste.defer(100, this);
724 }else if(Roo.isSafari){
730 this.execCmd('InsertText','\t');
734 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
735 this.cleanUpPaste.defer(100, this);
743 getAllAncestors: function()
745 var p = this.getSelectedNode();
748 a.push(p); // push blank onto stack..
749 p = this.getParentElement();
753 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
757 a.push(this.doc.body);
764 getSelection : function()
767 return Roo.isIE ? this.doc.selection : this.win.getSelection();
770 getSelectedNode: function()
772 // this may only work on Gecko!!!
774 // should we cache this!!!!
779 var range = this.createRange(this.getSelection()).cloneRange();
782 var parent = range.parentElement();
784 var testRange = range.duplicate();
785 testRange.moveToElementText(parent);
786 if (testRange.inRange(range)) {
789 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
792 parent = parent.parentElement;
797 // is ancestor a text element.
798 var ac = range.commonAncestorContainer;
799 if (ac.nodeType == 3) {
803 var ar = ac.childNodes;
806 var other_nodes = [];
807 var has_other_nodes = false;
808 for (var i=0;i<ar.length;i++) {
809 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
812 // fullly contained node.
814 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
819 // probably selected..
820 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
821 other_nodes.push(ar[i]);
825 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
830 has_other_nodes = true;
832 if (!nodes.length && other_nodes.length) {
835 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
841 createRange: function(sel)
843 // this has strange effects when using with
844 // top toolbar - not sure if it's a great idea.
845 //this.editor.contentWindow.focus();
846 if (typeof sel != "undefined") {
848 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
850 return this.doc.createRange();
853 return this.doc.createRange();
856 getParentElement: function()
860 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
862 var range = this.createRange(sel);
865 var p = range.commonAncestorContainer;
866 while (p.nodeType == 3) { // text node
877 * Range intersection.. the hard stuff...
881 * [ -- selected range --- ]
885 * if end is before start or hits it. fail.
886 * if start is after end or hits it fail.
888 * if either hits (but other is outside. - then it's not
894 // @see http://www.thismuchiknow.co.uk/?p=64.
895 rangeIntersectsNode : function(range, node)
897 var nodeRange = node.ownerDocument.createRange();
899 nodeRange.selectNode(node);
901 nodeRange.selectNodeContents(node);
904 var rangeStartRange = range.cloneRange();
905 rangeStartRange.collapse(true);
907 var rangeEndRange = range.cloneRange();
908 rangeEndRange.collapse(false);
910 var nodeStartRange = nodeRange.cloneRange();
911 nodeStartRange.collapse(true);
913 var nodeEndRange = nodeRange.cloneRange();
914 nodeEndRange.collapse(false);
916 return rangeStartRange.compareBoundaryPoints(
917 Range.START_TO_START, nodeEndRange) == -1 &&
918 rangeEndRange.compareBoundaryPoints(
919 Range.START_TO_START, nodeStartRange) == 1;
923 rangeCompareNode : function(range, node)
925 var nodeRange = node.ownerDocument.createRange();
927 nodeRange.selectNode(node);
929 nodeRange.selectNodeContents(node);
933 range.collapse(true);
935 nodeRange.collapse(true);
937 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
938 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
940 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
942 var nodeIsBefore = ss == 1;
943 var nodeIsAfter = ee == -1;
945 if (nodeIsBefore && nodeIsAfter)
947 if (!nodeIsBefore && nodeIsAfter)
948 return 1; //right trailed.
950 if (nodeIsBefore && !nodeIsAfter)
951 return 2; // left trailed.
956 // private? - in a new class?
957 cleanUpPaste : function()
959 // cleans up the whole document..
960 Roo.log('cleanuppaste');
962 this.cleanUpChildren(this.doc.body);
963 var clean = this.cleanWordChars(this.doc.body.innerHTML);
964 if (clean != this.doc.body.innerHTML) {
965 this.doc.body.innerHTML = clean;
970 cleanWordChars : function(input) {// change the chars to hex code
971 var he = Roo.HtmlEditorCore;
974 Roo.each(he.swapCodes, function(sw) {
975 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
977 output = output.replace(swapper, sw[1]);
984 cleanUpChildren : function (n)
986 if (!n.childNodes.length) {
989 for (var i = n.childNodes.length-1; i > -1 ; i--) {
990 this.cleanUpChild(n.childNodes[i]);
997 cleanUpChild : function (node)
1000 //console.log(node);
1001 if (node.nodeName == "#text") {
1002 // clean up silly Windows -- stuff?
1005 if (node.nodeName == "#comment") {
1006 node.parentNode.removeChild(node);
1007 // clean up silly Windows -- stuff?
1010 var lcname = node.tagName.toLowerCase();
1011 // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full
1012 // whitelist of tags..
1014 if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
1016 node.parentNode.removeChild(node);
1021 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1023 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1024 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1026 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1027 // remove_keep_children = true;
1030 if (remove_keep_children) {
1031 this.cleanUpChildren(node);
1032 // inserts everything just before this node...
1033 while (node.childNodes.length) {
1034 var cn = node.childNodes[0];
1035 node.removeChild(cn);
1036 node.parentNode.insertBefore(cn, node);
1038 node.parentNode.removeChild(node);
1042 if (!node.attributes || !node.attributes.length) {
1043 this.cleanUpChildren(node);
1047 function cleanAttr(n,v)
1050 if (v.match(/^\./) || v.match(/^\//)) {
1053 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1056 if (v.match(/^#/)) {
1059 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1060 node.removeAttribute(n);
1064 var cwhite = this.cwhite;
1065 var cblack = this.cblack;
1067 function cleanStyle(n,v)
1069 if (v.match(/expression/)) { //XSS?? should we even bother..
1070 node.removeAttribute(n);
1074 var parts = v.split(/;/);
1077 Roo.each(parts, function(p) {
1078 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1082 var l = p.split(':').shift().replace(/\s+/g,'');
1083 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1085 if ( cwhite.length && cblack.indexOf(l) > -1) {
1086 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1087 //node.removeAttribute(n);
1091 // only allow 'c whitelisted system attributes'
1092 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1093 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1094 //node.removeAttribute(n);
1105 node.setAttribute(n, clean.join(';'));
1107 node.removeAttribute(n);
1113 for (var i = node.attributes.length-1; i > -1 ; i--) {
1114 var a = node.attributes[i];
1117 if (a.name.toLowerCase().substr(0,2)=='on') {
1118 node.removeAttribute(a.name);
1121 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1122 node.removeAttribute(a.name);
1125 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1126 cleanAttr(a.name,a.value); // fixme..
1129 if (a.name == 'style') {
1130 cleanStyle(a.name,a.value);
1133 /// clean up MS crap..
1134 // tecnically this should be a list of valid class'es..
1137 if (a.name == 'class') {
1138 if (a.value.match(/^Mso/)) {
1139 node.className = '';
1142 if (a.value.match(/body/)) {
1143 node.className = '';
1154 this.cleanUpChildren(node);
1159 * Clean up MS wordisms...
1161 cleanWord : function(node)
1164 var cleanWordChildren = function()
1166 if (!node.childNodes.length) {
1169 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1170 _t.cleanWord(node.childNodes[i]);
1176 this.cleanWord(this.doc.body);
1179 if (node.nodeName == "#text") {
1180 // clean up silly Windows -- stuff?
1183 if (node.nodeName == "#comment") {
1184 node.parentNode.removeChild(node);
1185 // clean up silly Windows -- stuff?
1189 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1190 node.parentNode.removeChild(node);
1194 // remove - but keep children..
1195 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1196 while (node.childNodes.length) {
1197 var cn = node.childNodes[0];
1198 node.removeChild(cn);
1199 node.parentNode.insertBefore(cn, node);
1201 node.parentNode.removeChild(node);
1202 cleanWordChildren();
1206 if (node.className.length) {
1208 var cn = node.className.split(/\W+/);
1210 Roo.each(cn, function(cls) {
1211 if (cls.match(/Mso[a-zA-Z]+/)) {
1216 node.className = cna.length ? cna.join(' ') : '';
1218 node.removeAttribute("class");
1222 if (node.hasAttribute("lang")) {
1223 node.removeAttribute("lang");
1226 if (node.hasAttribute("style")) {
1228 var styles = node.getAttribute("style").split(";");
1230 Roo.each(styles, function(s) {
1231 if (!s.match(/:/)) {
1234 var kv = s.split(":");
1235 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1238 // what ever is left... we allow.
1241 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1242 if (!nstyle.length) {
1243 node.removeAttribute('style');
1247 cleanWordChildren();
1251 domToHTML : function(currentElement, depth, nopadtext) {
1254 nopadtext = nopadtext || false;
1256 if (!currentElement) {
1257 return this.domToHTML(this.doc.body);
1260 //Roo.log(currentElement);
1262 var allText = false;
1263 var nodeName = currentElement.nodeName;
1264 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1266 if (nodeName == '#text') {
1268 return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim();
1273 if (nodeName != 'BODY') {
1276 // Prints the node tagName, such as <A>, <IMG>, etc
1279 for(i = 0; i < currentElement.attributes.length;i++) {
1281 var aname = currentElement.attributes.item(i).name;
1282 if (!currentElement.attributes.item(i).value.length) {
1285 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1288 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1297 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1300 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1305 // Traverse the tree
1307 var currentElementChild = currentElement.childNodes.item(i);
1311 while (currentElementChild) {
1312 // Formatting code (indent the tree so it looks nice on the screen)
1313 var nopad = nopadtext;
1314 if (lastnode == 'SPAN') {
1318 if (currentElementChild.nodeName == '#text') {
1319 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1320 toadd = nopadtext ? toadd : toadd.trim();
1321 if (!nopad && toadd.length > 80) {
1322 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1327 currentElementChild = currentElement.childNodes.item(i);
1333 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1335 // Recursively traverse the tree structure of the child node
1336 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1337 lastnode = currentElementChild.nodeName;
1339 currentElementChild=currentElement.childNodes.item(i);
1345 // The remaining code is mostly for formatting the tree
1346 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1351 ret+= "</"+tagName+">";
1357 applyBlacklists : function()
1359 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1360 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1364 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1365 if (b.indexOf(tag) > -1) {
1368 this.white.push(tag);
1372 Roo.each(w, function(tag) {
1373 if (b.indexOf(tag) > -1) {
1376 if (this.white.indexOf(tag) > -1) {
1379 this.white.push(tag);
1384 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1385 if (w.indexOf(tag) > -1) {
1388 this.black.push(tag);
1392 Roo.each(b, function(tag) {
1393 if (w.indexOf(tag) > -1) {
1396 if (this.black.indexOf(tag) > -1) {
1399 this.black.push(tag);
1404 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1405 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1409 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1410 if (b.indexOf(tag) > -1) {
1413 this.cwhite.push(tag);
1417 Roo.each(w, function(tag) {
1418 if (b.indexOf(tag) > -1) {
1421 if (this.cwhite.indexOf(tag) > -1) {
1424 this.cwhite.push(tag);
1429 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1430 if (w.indexOf(tag) > -1) {
1433 this.cblack.push(tag);
1437 Roo.each(b, function(tag) {
1438 if (w.indexOf(tag) > -1) {
1441 if (this.cblack.indexOf(tag) > -1) {
1444 this.cblack.push(tag);
1449 setStylesheets : function(stylesheets)
1451 if(typeof(stylesheets) == 'string'){
1452 Roo.get(this.iframe.contentDocument.head).createChild({
1459 // Roo.each(this.stylesheets, function(s) {
1460 // st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
1466 removeStylesheets : function()
1471 // hide stuff that is not compatible
1489 * @cfg {String} fieldClass @hide
1492 * @cfg {String} focusClass @hide
1495 * @cfg {String} autoCreate @hide
1498 * @cfg {String} inputType @hide
1501 * @cfg {String} invalidClass @hide
1504 * @cfg {String} invalidText @hide
1507 * @cfg {String} msgFx @hide
1510 * @cfg {String} validateOnBlur @hide
1514 Roo.HtmlEditorCore.white = [
1515 'area', 'br', 'img', 'input', 'hr', 'wbr',
1517 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1518 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1519 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1520 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1521 'table', 'ul', 'xmp',
1523 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1526 'dir', 'menu', 'ol', 'ul', 'dl',
1532 Roo.HtmlEditorCore.black = [
1533 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1535 'base', 'basefont', 'bgsound', 'blink', 'body',
1536 'frame', 'frameset', 'head', 'html', 'ilayer',
1537 'iframe', 'layer', 'link', 'meta', 'object',
1538 'script', 'style' ,'title', 'xml' // clean later..
1540 Roo.HtmlEditorCore.clean = [
1541 'script', 'style', 'title', 'xml'
1543 Roo.HtmlEditorCore.remove = [
1548 Roo.HtmlEditorCore.ablack = [
1552 Roo.HtmlEditorCore.aclean = [
1553 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1557 Roo.HtmlEditorCore.pwhite= [
1558 'http', 'https', 'mailto'
1561 // white listed style attributes.
1562 Roo.HtmlEditorCore.cwhite= [
1563 // 'text-align', /// default is to allow most things..
1569 // black listed style attributes.
1570 Roo.HtmlEditorCore.cblack= [
1571 // 'font-size' -- this can be set by the project
1575 Roo.HtmlEditorCore.swapCodes =[