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(){
146 Roo.log(this.stylesheets);
148 // inherit styels from page...??
149 if (this.stylesheets === false) {
151 Roo.get(document.head).select('style').each(function(node) {
152 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
155 Roo.get(document.head).select('link').each(function(node) {
156 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
159 } else if (!this.stylesheets.length) {
161 st = '<style type="text/css">' +
162 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
165 // Roo.each(this.stylesheets, function(s) {
166 // st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
168 // this.setStylesheets(this.stylesheets);
171 st += '<style type="text/css">' +
172 'IMG { cursor: pointer } ' +
176 return '<html><head>' + st +
177 //<style type="text/css">' +
178 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
180 ' </head><body class="roo-htmleditor-body"></body></html>';
184 onRender : function(ct, position)
187 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
188 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
191 this.el.dom.style.border = '0 none';
192 this.el.dom.setAttribute('tabIndex', -1);
193 this.el.addClass('x-hidden hide');
197 if(Roo.isIE){ // fix IE 1px bogus margin
198 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
202 this.frameId = Roo.id();
206 var iframe = this.owner.wrap.createChild({
208 cls: 'form-control', // bootstrap..
212 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
217 this.iframe = iframe.dom;
221 this.doc.designMode = 'on';
224 this.doc.write(this.getDocMarkup());
228 var task = { // must defer to wait for browser to be ready
230 //console.log("run task?" + this.doc.readyState);
232 if(this.doc.body || this.doc.readyState == 'complete'){
234 this.doc.designMode="on";
238 Roo.TaskMgr.stop(task);
239 this.initEditor.defer(10, this);
246 Roo.TaskMgr.start(task);
248 if(this.stylesheets){
249 this.setStylesheets(this.stylesheets);
255 onResize : function(w, h)
257 Roo.log('resize: ' +w + ',' + h );
258 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
262 if(typeof w == 'number'){
264 this.iframe.style.width = w + 'px';
266 if(typeof h == 'number'){
268 this.iframe.style.height = h + 'px';
270 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
277 * Toggles the editor between standard and source edit mode.
278 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
280 toggleSourceEdit : function(sourceEditMode){
282 this.sourceEditMode = sourceEditMode === true;
284 if(this.sourceEditMode){
286 Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
289 Roo.get(this.iframe).removeClass(['x-hidden','hide']);
290 //this.iframe.className = '';
293 //this.setSize(this.owner.wrap.getSize());
294 //this.fireEvent('editmodechange', this, this.sourceEditMode);
301 * Protected method that will not generally be called directly. If you need/want
302 * custom HTML cleanup, this is the method you should override.
303 * @param {String} html The HTML to be cleaned
304 * return {String} The cleaned HTML
306 cleanHtml : function(html){
309 if(Roo.isSafari){ // strip safari nonsense
310 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
313 if(html == ' '){
320 * HTML Editor -> Textarea
321 * Protected method that will not generally be called directly. Syncs the contents
322 * of the editor iframe with the textarea.
324 syncValue : function(){
325 if(this.initialized){
326 var bd = (this.doc.body || this.doc.documentElement);
327 //this.cleanUpPaste(); -- this is done else where and causes havoc..
328 var html = bd.innerHTML;
330 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
331 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
333 html = '<div style="'+m[0]+'">' + html + '</div>';
336 html = this.cleanHtml(html);
337 // fix up the special chars.. normaly like back quotes in word...
338 // however we do not want to do this with chinese..
339 html = html.replace(/([\x80-\uffff])/g, function (a, b) {
340 var cc = b.charCodeAt();
342 (cc >= 0x4E00 && cc < 0xA000 ) ||
343 (cc >= 0x3400 && cc < 0x4E00 ) ||
344 (cc >= 0xf900 && cc < 0xfb00 )
350 if(this.owner.fireEvent('beforesync', this, html) !== false){
351 this.el.dom.value = html;
352 this.owner.fireEvent('sync', this, html);
358 * Protected method that will not generally be called directly. Pushes the value of the textarea
359 * into the iframe editor.
361 pushValue : function(){
362 if(this.initialized){
363 var v = this.el.dom.value.trim();
369 if(this.owner.fireEvent('beforepush', this, v) !== false){
370 var d = (this.doc.body || this.doc.documentElement);
373 this.el.dom.value = d.innerHTML;
374 this.owner.fireEvent('push', this, v);
380 deferFocus : function(){
381 this.focus.defer(10, this);
386 if(this.win && !this.sourceEditMode){
393 assignDocWin: function()
395 var iframe = this.iframe;
398 this.doc = iframe.contentWindow.document;
399 this.win = iframe.contentWindow;
401 // if (!Roo.get(this.frameId)) {
404 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
405 // this.win = Roo.get(this.frameId).dom.contentWindow;
407 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
411 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
412 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
417 initEditor : function(){
418 //console.log("INIT EDITOR");
423 this.doc.designMode="on";
425 this.doc.write(this.getDocMarkup());
428 var dbody = (this.doc.body || this.doc.documentElement);
429 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
430 // this copies styles from the containing element into thsi one..
431 // not sure why we need all of this..
432 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
434 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
435 //ss['background-attachment'] = 'fixed'; // w3c
436 dbody.bgProperties = 'fixed'; // ie
437 //Roo.DomHelper.applyStyles(dbody, ss);
438 Roo.EventManager.on(this.doc, {
439 //'mousedown': this.onEditorEvent,
440 'mouseup': this.onEditorEvent,
441 'dblclick': this.onEditorEvent,
442 'click': this.onEditorEvent,
443 'keyup': this.onEditorEvent,
448 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
450 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
451 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
453 this.initialized = true;
455 this.owner.fireEvent('initialize', this);
460 onDestroy : function(){
466 //for (var i =0; i < this.toolbars.length;i++) {
467 // // fixme - ask toolbars for heights?
468 // this.toolbars[i].onDestroy();
471 //this.wrap.dom.innerHTML = '';
472 //this.wrap.remove();
477 onFirstFocus : function(){
482 this.activated = true;
485 if(Roo.isGecko){ // prevent silly gecko errors
487 var s = this.win.getSelection();
488 if(!s.focusNode || s.focusNode.nodeType != 3){
489 var r = s.getRangeAt(0);
490 r.selectNodeContents((this.doc.body || this.doc.documentElement));
495 this.execCmd('useCSS', true);
496 this.execCmd('styleWithCSS', false);
499 this.owner.fireEvent('activate', this);
503 adjustFont: function(btn){
504 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
505 //if(Roo.isSafari){ // safari
508 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
509 if(Roo.isSafari){ // safari
510 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
511 v = (v < 10) ? 10 : v;
512 v = (v > 48) ? 48 : v;
513 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
518 v = Math.max(1, v+adjust);
520 this.execCmd('FontSize', v );
523 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)
605 var r = this.doc.selection.createRange();
616 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
620 // from jquery ui (MIT licenced)
624 if (win.getSelection && win.getSelection().getRangeAt) {
625 range = win.getSelection().getRangeAt(0);
626 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
627 range.insertNode(node);
628 } else if (win.document.selection && win.document.selection.createRange) {
629 // no firefox support
630 var txt = typeof(text) == 'string' ? text : text.outerHTML;
631 win.document.selection.createRange().pasteHTML(txt);
633 // no firefox support
634 var txt = typeof(text) == 'string' ? text : text.outerHTML;
635 this.execCmd('InsertHTML', txt);
644 mozKeyPress : function(e){
646 var c = e.getCharCode(), cmd;
649 c = String.fromCharCode(c).toLowerCase();
663 this.cleanUpPaste.defer(100, this);
679 fixKeys : function(){ // load time branching for fastest keydown performance
682 var k = e.getKey(), r;
685 r = this.doc.selection.createRange();
688 r.pasteHTML('    ');
695 r = this.doc.selection.createRange();
697 var target = r.parentElement();
698 if(!target || target.tagName.toLowerCase() != 'li'){
700 r.pasteHTML('<br />');
706 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
707 this.cleanUpPaste.defer(100, this);
713 }else if(Roo.isOpera){
719 this.execCmd('InsertHTML','    ');
722 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
723 this.cleanUpPaste.defer(100, this);
728 }else if(Roo.isSafari){
734 this.execCmd('InsertText','\t');
738 if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
739 this.cleanUpPaste.defer(100, this);
747 getAllAncestors: function()
749 var p = this.getSelectedNode();
752 a.push(p); // push blank onto stack..
753 p = this.getParentElement();
757 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
761 a.push(this.doc.body);
768 getSelection : function()
771 return Roo.isIE ? this.doc.selection : this.win.getSelection();
774 getSelectedNode: function()
776 // this may only work on Gecko!!!
778 // should we cache this!!!!
783 var range = this.createRange(this.getSelection()).cloneRange();
786 var parent = range.parentElement();
788 var testRange = range.duplicate();
789 testRange.moveToElementText(parent);
790 if (testRange.inRange(range)) {
793 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
796 parent = parent.parentElement;
801 // is ancestor a text element.
802 var ac = range.commonAncestorContainer;
803 if (ac.nodeType == 3) {
807 var ar = ac.childNodes;
810 var other_nodes = [];
811 var has_other_nodes = false;
812 for (var i=0;i<ar.length;i++) {
813 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
816 // fullly contained node.
818 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
823 // probably selected..
824 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
825 other_nodes.push(ar[i]);
829 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
834 has_other_nodes = true;
836 if (!nodes.length && other_nodes.length) {
839 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
845 createRange: function(sel)
847 // this has strange effects when using with
848 // top toolbar - not sure if it's a great idea.
849 //this.editor.contentWindow.focus();
850 if (typeof sel != "undefined") {
852 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
854 return this.doc.createRange();
857 return this.doc.createRange();
860 getParentElement: function()
864 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
866 var range = this.createRange(sel);
869 var p = range.commonAncestorContainer;
870 while (p.nodeType == 3) { // text node
881 * Range intersection.. the hard stuff...
885 * [ -- selected range --- ]
889 * if end is before start or hits it. fail.
890 * if start is after end or hits it fail.
892 * if either hits (but other is outside. - then it's not
898 // @see http://www.thismuchiknow.co.uk/?p=64.
899 rangeIntersectsNode : function(range, node)
901 var nodeRange = node.ownerDocument.createRange();
903 nodeRange.selectNode(node);
905 nodeRange.selectNodeContents(node);
908 var rangeStartRange = range.cloneRange();
909 rangeStartRange.collapse(true);
911 var rangeEndRange = range.cloneRange();
912 rangeEndRange.collapse(false);
914 var nodeStartRange = nodeRange.cloneRange();
915 nodeStartRange.collapse(true);
917 var nodeEndRange = nodeRange.cloneRange();
918 nodeEndRange.collapse(false);
920 return rangeStartRange.compareBoundaryPoints(
921 Range.START_TO_START, nodeEndRange) == -1 &&
922 rangeEndRange.compareBoundaryPoints(
923 Range.START_TO_START, nodeStartRange) == 1;
927 rangeCompareNode : function(range, node)
929 var nodeRange = node.ownerDocument.createRange();
931 nodeRange.selectNode(node);
933 nodeRange.selectNodeContents(node);
937 range.collapse(true);
939 nodeRange.collapse(true);
941 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
942 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
944 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
946 var nodeIsBefore = ss == 1;
947 var nodeIsAfter = ee == -1;
949 if (nodeIsBefore && nodeIsAfter)
951 if (!nodeIsBefore && nodeIsAfter)
952 return 1; //right trailed.
954 if (nodeIsBefore && !nodeIsAfter)
955 return 2; // left trailed.
960 // private? - in a new class?
961 cleanUpPaste : function()
963 // cleans up the whole document..
964 Roo.log('cleanuppaste');
966 this.cleanUpChildren(this.doc.body);
967 var clean = this.cleanWordChars(this.doc.body.innerHTML);
968 if (clean != this.doc.body.innerHTML) {
969 this.doc.body.innerHTML = clean;
974 cleanWordChars : function(input) {// change the chars to hex code
975 var he = Roo.HtmlEditorCore;
978 Roo.each(he.swapCodes, function(sw) {
979 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
981 output = output.replace(swapper, sw[1]);
988 cleanUpChildren : function (n)
990 if (!n.childNodes.length) {
993 for (var i = n.childNodes.length-1; i > -1 ; i--) {
994 this.cleanUpChild(n.childNodes[i]);
1001 cleanUpChild : function (node)
1004 //console.log(node);
1005 if (node.nodeName == "#text") {
1006 // clean up silly Windows -- stuff?
1009 if (node.nodeName == "#comment") {
1010 node.parentNode.removeChild(node);
1011 // clean up silly Windows -- stuff?
1014 var lcname = node.tagName.toLowerCase();
1015 // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full
1016 // whitelist of tags..
1018 if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
1020 node.parentNode.removeChild(node);
1025 var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
1027 // remove <a name=....> as rendering on yahoo mailer is borked with this.
1028 // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
1030 //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
1031 // remove_keep_children = true;
1034 if (remove_keep_children) {
1035 this.cleanUpChildren(node);
1036 // inserts everything just before this node...
1037 while (node.childNodes.length) {
1038 var cn = node.childNodes[0];
1039 node.removeChild(cn);
1040 node.parentNode.insertBefore(cn, node);
1042 node.parentNode.removeChild(node);
1046 if (!node.attributes || !node.attributes.length) {
1047 this.cleanUpChildren(node);
1051 function cleanAttr(n,v)
1054 if (v.match(/^\./) || v.match(/^\//)) {
1057 if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
1060 if (v.match(/^#/)) {
1063 // Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
1064 node.removeAttribute(n);
1068 var cwhite = this.cwhite;
1069 var cblack = this.cblack;
1071 function cleanStyle(n,v)
1073 if (v.match(/expression/)) { //XSS?? should we even bother..
1074 node.removeAttribute(n);
1078 var parts = v.split(/;/);
1081 Roo.each(parts, function(p) {
1082 p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
1086 var l = p.split(':').shift().replace(/\s+/g,'');
1087 l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
1089 if ( cwhite.length && cblack.indexOf(l) > -1) {
1090 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1091 //node.removeAttribute(n);
1095 // only allow 'c whitelisted system attributes'
1096 if ( cwhite.length && cwhite.indexOf(l) < 0) {
1097 // Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
1098 //node.removeAttribute(n);
1109 node.setAttribute(n, clean.join(';'));
1111 node.removeAttribute(n);
1117 for (var i = node.attributes.length-1; i > -1 ; i--) {
1118 var a = node.attributes[i];
1121 if (a.name.toLowerCase().substr(0,2)=='on') {
1122 node.removeAttribute(a.name);
1125 if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
1126 node.removeAttribute(a.name);
1129 if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
1130 cleanAttr(a.name,a.value); // fixme..
1133 if (a.name == 'style') {
1134 cleanStyle(a.name,a.value);
1137 /// clean up MS crap..
1138 // tecnically this should be a list of valid class'es..
1141 if (a.name == 'class') {
1142 if (a.value.match(/^Mso/)) {
1143 node.className = '';
1146 if (a.value.match(/body/)) {
1147 node.className = '';
1158 this.cleanUpChildren(node);
1163 * Clean up MS wordisms...
1165 cleanWord : function(node)
1168 var cleanWordChildren = function()
1170 if (!node.childNodes.length) {
1173 for (var i = node.childNodes.length-1; i > -1 ; i--) {
1174 _t.cleanWord(node.childNodes[i]);
1180 this.cleanWord(this.doc.body);
1183 if (node.nodeName == "#text") {
1184 // clean up silly Windows -- stuff?
1187 if (node.nodeName == "#comment") {
1188 node.parentNode.removeChild(node);
1189 // clean up silly Windows -- stuff?
1193 if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
1194 node.parentNode.removeChild(node);
1198 // remove - but keep children..
1199 if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
1200 while (node.childNodes.length) {
1201 var cn = node.childNodes[0];
1202 node.removeChild(cn);
1203 node.parentNode.insertBefore(cn, node);
1205 node.parentNode.removeChild(node);
1206 cleanWordChildren();
1210 if (node.className.length) {
1212 var cn = node.className.split(/\W+/);
1214 Roo.each(cn, function(cls) {
1215 if (cls.match(/Mso[a-zA-Z]+/)) {
1220 node.className = cna.length ? cna.join(' ') : '';
1222 node.removeAttribute("class");
1226 if (node.hasAttribute("lang")) {
1227 node.removeAttribute("lang");
1230 if (node.hasAttribute("style")) {
1232 var styles = node.getAttribute("style").split(";");
1234 Roo.each(styles, function(s) {
1235 if (!s.match(/:/)) {
1238 var kv = s.split(":");
1239 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
1242 // what ever is left... we allow.
1245 node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
1246 if (!nstyle.length) {
1247 node.removeAttribute('style');
1251 cleanWordChildren();
1255 domToHTML : function(currentElement, depth, nopadtext) {
1258 nopadtext = nopadtext || false;
1260 if (!currentElement) {
1261 return this.domToHTML(this.doc.body);
1264 //Roo.log(currentElement);
1266 var allText = false;
1267 var nodeName = currentElement.nodeName;
1268 var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
1270 if (nodeName == '#text') {
1272 return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim();
1277 if (nodeName != 'BODY') {
1280 // Prints the node tagName, such as <A>, <IMG>, etc
1283 for(i = 0; i < currentElement.attributes.length;i++) {
1285 var aname = currentElement.attributes.item(i).name;
1286 if (!currentElement.attributes.item(i).value.length) {
1289 attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
1292 ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
1301 if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
1304 if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
1309 // Traverse the tree
1311 var currentElementChild = currentElement.childNodes.item(i);
1315 while (currentElementChild) {
1316 // Formatting code (indent the tree so it looks nice on the screen)
1317 var nopad = nopadtext;
1318 if (lastnode == 'SPAN') {
1322 if (currentElementChild.nodeName == '#text') {
1323 var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
1324 toadd = nopadtext ? toadd : toadd.trim();
1325 if (!nopad && toadd.length > 80) {
1326 innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
1331 currentElementChild = currentElement.childNodes.item(i);
1337 innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
1339 // Recursively traverse the tree structure of the child node
1340 innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
1341 lastnode = currentElementChild.nodeName;
1343 currentElementChild=currentElement.childNodes.item(i);
1349 // The remaining code is mostly for formatting the tree
1350 ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
1355 ret+= "</"+tagName+">";
1361 applyBlacklists : function()
1363 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1364 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1368 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1369 if (b.indexOf(tag) > -1) {
1372 this.white.push(tag);
1376 Roo.each(w, function(tag) {
1377 if (b.indexOf(tag) > -1) {
1380 if (this.white.indexOf(tag) > -1) {
1383 this.white.push(tag);
1388 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1389 if (w.indexOf(tag) > -1) {
1392 this.black.push(tag);
1396 Roo.each(b, function(tag) {
1397 if (w.indexOf(tag) > -1) {
1400 if (this.black.indexOf(tag) > -1) {
1403 this.black.push(tag);
1408 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1409 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1413 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1414 if (b.indexOf(tag) > -1) {
1417 this.cwhite.push(tag);
1421 Roo.each(w, function(tag) {
1422 if (b.indexOf(tag) > -1) {
1425 if (this.cwhite.indexOf(tag) > -1) {
1428 this.cwhite.push(tag);
1433 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1434 if (w.indexOf(tag) > -1) {
1437 this.cblack.push(tag);
1441 Roo.each(b, function(tag) {
1442 if (w.indexOf(tag) > -1) {
1445 if (this.cblack.indexOf(tag) > -1) {
1448 this.cblack.push(tag);
1453 setStylesheets : function(href)
1455 // Roo.each(this.stylesheets, function(s) {
1456 // st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
1459 Roo.get(this.iframe.contentDocument.head).createChild({
1467 // hide stuff that is not compatible
1485 * @cfg {String} fieldClass @hide
1488 * @cfg {String} focusClass @hide
1491 * @cfg {String} autoCreate @hide
1494 * @cfg {String} inputType @hide
1497 * @cfg {String} invalidClass @hide
1500 * @cfg {String} invalidText @hide
1503 * @cfg {String} msgFx @hide
1506 * @cfg {String} validateOnBlur @hide
1510 Roo.HtmlEditorCore.white = [
1511 'area', 'br', 'img', 'input', 'hr', 'wbr',
1513 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
1514 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
1515 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
1516 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
1517 'table', 'ul', 'xmp',
1519 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
1522 'dir', 'menu', 'ol', 'ul', 'dl',
1528 Roo.HtmlEditorCore.black = [
1529 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1531 'base', 'basefont', 'bgsound', 'blink', 'body',
1532 'frame', 'frameset', 'head', 'html', 'ilayer',
1533 'iframe', 'layer', 'link', 'meta', 'object',
1534 'script', 'style' ,'title', 'xml' // clean later..
1536 Roo.HtmlEditorCore.clean = [
1537 'script', 'style', 'title', 'xml'
1539 Roo.HtmlEditorCore.remove = [
1544 Roo.HtmlEditorCore.ablack = [
1548 Roo.HtmlEditorCore.aclean = [
1549 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1553 Roo.HtmlEditorCore.pwhite= [
1554 'http', 'https', 'mailto'
1557 // white listed style attributes.
1558 Roo.HtmlEditorCore.cwhite= [
1559 // 'text-align', /// default is to allow most things..
1565 // black listed style attributes.
1566 Roo.HtmlEditorCore.cblack= [
1567 // 'font-size' -- this can be set by the project
1571 Roo.HtmlEditorCore.swapCodes =[