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.
118 * @cfg {boolean} allowComments - default false - allow comments in HTML source - by default they are stripped - if you are editing email you may need this.
120 allowComments: false,
124 // private properties
125 validationEvent : false,
129 sourceEditMode : false,
130 onFocus : Roo.emptyFn,
136 // blacklist + whitelisted elements..
145 * Protected method that will not generally be called directly. It
146 * is called when the editor initializes the iframe with HTML contents. Override this method if you
147 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
149 getDocMarkup : function(){
153 // inherit styels from page...??
154 if (this.stylesheets === false) {
156 Roo.get(document.head).select('style').each(function(node) {
157 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
160 Roo.get(document.head).select('link').each(function(node) {
161 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
164 } else if (!this.stylesheets.length) {
166 st = '<style type="text/css">' +
167 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
170 for (var i in this.stylesheets) {
171 if (typeof(this.stylesheets[i]) != 'string') {
174 st += '<link rel="stylesheet" href="' + this.stylesheets[i] +'" type="text/css">';
179 st += '<style type="text/css">' +
180 'IMG { cursor: pointer } ' +
183 var cls = 'roo-htmleditor-body';
185 if(this.bodyCls.length){
186 cls += ' ' + this.bodyCls;
189 return '<html><head>' + st +
190 //<style type="text/css">' +
191 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
193 ' </head><body contenteditable="true" data-enable-grammerly="true" class="' + cls + '"></body></html>';
197 onRender : function(ct, position)
200 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
201 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
204 this.el.dom.style.border = '0 none';
205 this.el.dom.setAttribute('tabIndex', -1);
206 this.el.addClass('x-hidden hide');
210 if(Roo.isIE){ // fix IE 1px bogus margin
211 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
215 this.frameId = Roo.id();
219 var iframe = this.owner.wrap.createChild({
221 cls: 'form-control', // bootstrap..
225 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
230 this.iframe = iframe.dom;
234 this.doc.designMode = 'on';
237 this.doc.write(this.getDocMarkup());
241 var task = { // must defer to wait for browser to be ready
243 //console.log("run task?" + this.doc.readyState);
245 if(this.doc.body || this.doc.readyState == 'complete'){
247 this.doc.designMode="on";
252 Roo.TaskMgr.stop(task);
253 this.initEditor.defer(10, this);
260 Roo.TaskMgr.start(task);
265 onResize : function(w, h)
267 Roo.log('resize: ' +w + ',' + h );
268 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
272 if(typeof w == 'number'){
274 this.iframe.style.width = w + 'px';
276 if(typeof h == 'number'){
278 this.iframe.style.height = h + 'px';
280 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
287 * Toggles the editor between standard and source edit mode.
288 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
290 toggleSourceEdit : function(sourceEditMode){
292 this.sourceEditMode = sourceEditMode === true;
294 if(this.sourceEditMode){
296 Roo.get(this.iframe).addClass(['x-hidden','hide', 'd-none']); //FIXME - what's the BS styles for these
299 Roo.get(this.iframe).removeClass(['x-hidden','hide', 'd-none']);
300 //this.iframe.className = '';
303 //this.setSize(this.owner.wrap.getSize());
304 //this.fireEvent('editmodechange', this, this.sourceEditMode);
311 * Protected method that will not generally be called directly. If you need/want
312 * custom HTML cleanup, this is the method you should override.
313 * @param {String} html The HTML to be cleaned
314 * return {String} The cleaned HTML
316 cleanHtml : function(html){
319 if(Roo.isSafari){ // strip safari nonsense
320 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
323 if(html == ' '){
330 * HTML Editor -> Textarea
331 * Protected method that will not generally be called directly. Syncs the contents
332 * of the editor iframe with the textarea.
334 syncValue : function()
336 Roo.log("HtmlEditorCore:syncValue (EDITOR->TEXT)");
337 if(this.initialized){
339 this.undoManager.addEvent();
342 var bd = (this.doc.body || this.doc.documentElement);
343 //this.cleanUpPaste(); -- this is done else where and causes havoc..
345 // not sure if this is really the place for this
346 // the blocks are synced occasionaly - since we currently dont add listeners on the blocks
347 // this has to update attributes that get duped.. like alt and caption..
350 //Roo.each(Roo.get(this.doc.body).query('*[data-block]'), function(e) {
351 // Roo.htmleditor.Block.factory(e);
355 var div = document.createElement('div');
356 div.innerHTML = bd.innerHTML;
357 // remove content editable. (blocks)
360 new Roo.htmleditor.FilterAttributes({node : div, attrib_black: [ 'contenteditable' ] });
362 var html = div.innerHTML;
364 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
365 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
367 html = '<div style="'+m[0]+'">' + html + '</div>';
370 html = this.cleanHtml(html);
371 // fix up the special chars.. normaly like back quotes in word...
372 // however we do not want to do this with chinese..
373 html = html.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u0080-\uFFFF]/g, function(match) {
375 var cc = match.charCodeAt();
377 // Get the character value, handling surrogate pairs
378 if (match.length == 2) {
379 // It's a surrogate pair, calculate the Unicode code point
380 var high = match.charCodeAt(0) - 0xD800;
381 var low = match.charCodeAt(1) - 0xDC00;
382 cc = (high * 0x400) + low + 0x10000;
384 (cc >= 0x4E00 && cc < 0xA000 ) ||
385 (cc >= 0x3400 && cc < 0x4E00 ) ||
386 (cc >= 0xf900 && cc < 0xfb00 )
391 // No, use a numeric entity. Here we brazenly (and possibly mistakenly)
392 return "&#" + cc + ";";
399 if(this.owner.fireEvent('beforesync', this, html) !== false){
400 this.el.dom.value = html;
401 this.owner.fireEvent('sync', this, html);
407 * TEXTAREA -> EDITABLE
408 * Protected method that will not generally be called directly. Pushes the value of the textarea
409 * into the iframe editor.
411 pushValue : function()
413 Roo.log("HtmlEditorCore:pushValue (TEXT->EDITOR)");
414 if(this.initialized){
415 var v = this.el.dom.value.trim();
418 if(this.owner.fireEvent('beforepush', this, v) !== false){
419 var d = (this.doc.body || this.doc.documentElement);
422 this.el.dom.value = d.innerHTML;
423 this.owner.fireEvent('push', this, v);
426 Roo.each(Roo.get(this.doc.body).query('*[data-block]'), function(e) {
428 Roo.htmleditor.Block.factory(e);
431 var lc = this.doc.body.lastChild;
432 if (lc && lc.nodeType == 1 && lc.getAttribute("contenteditable") == "false") {
433 // add an extra line at the end.
434 this.doc.body.appendChild(this.doc.createElement('br'));
442 deferFocus : function(){
443 this.focus.defer(10, this);
448 if(this.win && !this.sourceEditMode){
455 assignDocWin: function()
457 var iframe = this.iframe;
460 this.doc = iframe.contentWindow.document;
461 this.win = iframe.contentWindow;
463 // if (!Roo.get(this.frameId)) {
466 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
467 // this.win = Roo.get(this.frameId).dom.contentWindow;
469 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
473 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
474 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
479 initEditor : function(){
480 //console.log("INIT EDITOR");
485 this.doc.designMode="on";
487 this.doc.write(this.getDocMarkup());
490 var dbody = (this.doc.body || this.doc.documentElement);
491 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
492 // this copies styles from the containing element into thsi one..
493 // not sure why we need all of this..
494 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
496 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
497 //ss['background-attachment'] = 'fixed'; // w3c
498 dbody.bgProperties = 'fixed'; // ie
499 //Roo.DomHelper.applyStyles(dbody, ss);
500 Roo.EventManager.on(this.doc, {
501 //'mousedown': this.onEditorEvent,
502 'mouseup': this.onEditorEvent,
503 'dblclick': this.onEditorEvent,
504 'click': this.onEditorEvent,
505 'keyup': this.onEditorEvent,
510 Roo.EventManager.on(this.doc, {
511 'paste': this.onPasteEvent,
515 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
517 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
518 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
520 this.initialized = true;
523 // initialize special key events - enter
524 new Roo.htmleditor.KeyEnter({core : this});
528 this.owner.fireEvent('initialize', this);
532 onPasteEvent : function(e,v)
534 // I think we better assume paste is going to be a dirty load of rubish from word..
536 // even pasting into a 'email version' of this widget will have to clean up that mess.
537 var cd = (e.browserEvent.clipboardData || window.clipboardData);
539 // check what type of paste - if it's an image, then handle it differently.
540 if (cd.files.length > 0) {
542 var urlAPI = (window.createObjectURL && window) ||
543 (window.URL && URL.revokeObjectURL && URL) ||
544 (window.webkitURL && webkitURL);
546 var url = urlAPI.createObjectURL( cd.files[0]);
547 this.insertAtCursor('<img src=" + url + ">');
551 var html = cd.getData('text/html'); // clipboard event
552 var parser = new Roo.rtf.Parser(cd.getData('text/rtf'));
553 var images = parser.doc ? parser.doc.getElementsByType('pict') : [];
557 images = images.filter(function(g) { return !g.path.match(/^rtf\/(head|pgdsctbl|listtable)/); }) // ignore headers
558 .map(function(g) { return g.toDataURL(); });
561 html = this.cleanWordChars(html);
563 var d = (new DOMParser().parseFromString(html, 'text/html')).body;
565 if (images.length > 0) {
566 Roo.each(d.getElementsByTagName('img'), function(img, i) {
567 img.setAttribute('src', images[i]);
572 new Roo.htmleditor.FilterStyleToTag({ node : d });
573 new Roo.htmleditor.FilterAttributes({
575 attrib_white : ['href', 'src', 'name', 'align'],
576 attrib_clean : ['href', 'src' ]
578 new Roo.htmleditor.FilterBlack({ node : d, tag : this.black});
580 new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT' ]} );
581 new Roo.htmleditor.FilterParagraph({ node : d });
582 new Roo.htmleditor.FilterSpan({ node : d });
583 new Roo.htmleditor.FilterLongBr({ node : d });
587 this.insertAtCursor(d.innerHTML);
591 // default behaveiour should be our local cleanup paste? (optional?)
592 // for simple editor - we want to hammer the paste and get rid of everything... - so over-rideable..
593 //this.owner.fireEvent('paste', e, v);
596 onDestroy : function(){
602 //for (var i =0; i < this.toolbars.length;i++) {
603 // // fixme - ask toolbars for heights?
604 // this.toolbars[i].onDestroy();
607 //this.wrap.dom.innerHTML = '';
608 //this.wrap.remove();
613 onFirstFocus : function(){
616 this.undoManager = new Roo.lib.UndoManager(100,(this.doc.body || this.doc.documentElement));
618 this.activated = true;
621 if(Roo.isGecko){ // prevent silly gecko errors
623 var s = this.win.getSelection();
624 if(!s.focusNode || s.focusNode.nodeType != 3){
625 var r = s.getRangeAt(0);
626 r.selectNodeContents((this.doc.body || this.doc.documentElement));
631 this.execCmd('useCSS', true);
632 this.execCmd('styleWithCSS', false);
635 this.owner.fireEvent('activate', this);
639 adjustFont: function(btn){
640 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
641 //if(Roo.isSafari){ // safari
644 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
645 if(Roo.isSafari){ // safari
646 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
647 v = (v < 10) ? 10 : v;
648 v = (v > 48) ? 48 : v;
649 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
654 v = Math.max(1, v+adjust);
656 this.execCmd('FontSize', v );
659 onEditorEvent : function(e)
662 if (e && (e.ctrlKey || e.metaKey) && e.keyCode === 90) {
663 return; // we do not handle this.. (undo manager does..)
665 this.owner.fireEvent('editorevent', this, e);
666 // this.updateToolbar();
667 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
670 insertTag : function(tg)
672 // could be a bit smarter... -> wrap the current selected tRoo..
673 if (tg.toLowerCase() == 'span' ||
674 tg.toLowerCase() == 'code' ||
675 tg.toLowerCase() == 'sup' ||
676 tg.toLowerCase() == 'sub'
679 range = this.createRange(this.getSelection());
680 var wrappingNode = this.doc.createElement(tg.toLowerCase());
681 wrappingNode.appendChild(range.extractContents());
682 range.insertNode(wrappingNode);
689 this.execCmd("formatblock", tg);
690 this.undoManager.addEvent();
693 insertText : function(txt)
697 var range = this.createRange();
698 range.deleteContents();
699 //alert(Sender.getAttribute('label'));
701 range.insertNode(this.doc.createTextNode(txt));
702 this.undoManager.addEvent();
708 * Executes a Midas editor command on the editor document and performs necessary focus and
709 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
710 * @param {String} cmd The Midas command
711 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
713 relayCmd : function(cmd, value){
715 this.execCmd(cmd, value);
716 this.owner.fireEvent('editorevent', this);
717 //this.updateToolbar();
718 this.owner.deferFocus();
722 * Executes a Midas editor command directly on the editor document.
723 * For visual commands, you should use {@link #relayCmd} instead.
724 * <b>This should only be called after the editor is initialized.</b>
725 * @param {String} cmd The Midas command
726 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
728 execCmd : function(cmd, value){
729 this.doc.execCommand(cmd, false, value === undefined ? null : value);
736 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
738 * @param {String} text | dom node..
740 insertAtCursor : function(text)
747 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
751 // from jquery ui (MIT licenced)
755 if (win.getSelection && win.getSelection().getRangeAt) {
757 // delete the existing?
759 this.createRange(this.getSelection()).deleteContents();
760 range = win.getSelection().getRangeAt(0);
761 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
762 range.insertNode(node);
763 range = range.cloneRange();
764 range.collapse(false);
766 win.getSelection().removeAllRanges();
767 win.getSelection().addRange(range);
771 } else if (win.document.selection && win.document.selection.createRange) {
772 // no firefox support
773 var txt = typeof(text) == 'string' ? text : text.outerHTML;
774 win.document.selection.createRange().pasteHTML(txt);
777 // no firefox support
778 var txt = typeof(text) == 'string' ? text : text.outerHTML;
779 this.execCmd('InsertHTML', txt);
787 mozKeyPress : function(e){
789 var c = e.getCharCode(), cmd;
792 c = String.fromCharCode(c).toLowerCase();
806 // this.cleanUpPaste.defer(100, this);
822 fixKeys : function(){ // load time branching for fastest keydown performance
825 var k = e.getKey(), r;
828 r = this.doc.selection.createRange();
831 r.pasteHTML('    ');
838 r = this.doc.selection.createRange();
840 var target = r.parentElement();
841 if(!target || target.tagName.toLowerCase() != 'li'){
843 r.pasteHTML('<br/>');
849 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
850 // this.cleanUpPaste.defer(100, this);
856 }else if(Roo.isOpera){
862 this.execCmd('InsertHTML','    ');
865 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
866 // this.cleanUpPaste.defer(100, this);
871 }else if(Roo.isSafari){
877 this.execCmd('InsertText','\t');
881 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
882 // this.cleanUpPaste.defer(100, this);
890 getAllAncestors: function()
892 var p = this.getSelectedNode();
895 a.push(p); // push blank onto stack..
896 p = this.getParentElement();
900 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
904 a.push(this.doc.body);
911 getSelection : function()
914 return Roo.isIE ? this.doc.selection : this.win.getSelection();
918 * @param {DomElement} node the node to select
920 selectNode : function(node)
922 var nodeRange = node.ownerDocument.createRange();
924 nodeRange.selectNode(node);
926 nodeRange.selectNodeContents(node);
928 //nodeRange.collapse(true);
929 var s = this.win.getSelection();
931 s.addRange(nodeRange);
934 getSelectedNode: function()
936 // this may only work on Gecko!!!
938 // should we cache this!!!!
943 var range = this.createRange(this.getSelection()).cloneRange();
946 var parent = range.parentElement();
948 var testRange = range.duplicate();
949 testRange.moveToElementText(parent);
950 if (testRange.inRange(range)) {
953 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
956 parent = parent.parentElement;
961 // is ancestor a text element.
962 var ac = range.commonAncestorContainer;
963 if (ac.nodeType == 3) {
967 var ar = ac.childNodes;
970 var other_nodes = [];
971 var has_other_nodes = false;
972 for (var i=0;i<ar.length;i++) {
973 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
976 // fullly contained node.
978 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
983 // probably selected..
984 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
985 other_nodes.push(ar[i]);
989 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
994 has_other_nodes = true;
996 if (!nodes.length && other_nodes.length) {
999 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
1005 createRange: function(sel)
1007 // this has strange effects when using with
1008 // top toolbar - not sure if it's a great idea.
1009 //this.editor.contentWindow.focus();
1010 if (typeof sel != "undefined") {
1012 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
1014 return this.doc.createRange();
1017 return this.doc.createRange();
1020 getParentElement: function()
1023 this.assignDocWin();
1024 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
1026 var range = this.createRange(sel);
1029 var p = range.commonAncestorContainer;
1030 while (p.nodeType == 3) { // text node
1041 * Range intersection.. the hard stuff...
1045 * [ -- selected range --- ]
1049 * if end is before start or hits it. fail.
1050 * if start is after end or hits it fail.
1052 * if either hits (but other is outside. - then it's not
1058 // @see http://www.thismuchiknow.co.uk/?p=64.
1059 rangeIntersectsNode : function(range, node)
1061 var nodeRange = node.ownerDocument.createRange();
1063 nodeRange.selectNode(node);
1065 nodeRange.selectNodeContents(node);
1068 var rangeStartRange = range.cloneRange();
1069 rangeStartRange.collapse(true);
1071 var rangeEndRange = range.cloneRange();
1072 rangeEndRange.collapse(false);
1074 var nodeStartRange = nodeRange.cloneRange();
1075 nodeStartRange.collapse(true);
1077 var nodeEndRange = nodeRange.cloneRange();
1078 nodeEndRange.collapse(false);
1080 return rangeStartRange.compareBoundaryPoints(
1081 Range.START_TO_START, nodeEndRange) == -1 &&
1082 rangeEndRange.compareBoundaryPoints(
1083 Range.START_TO_START, nodeStartRange) == 1;
1087 rangeCompareNode : function(range, node)
1089 var nodeRange = node.ownerDocument.createRange();
1091 nodeRange.selectNode(node);
1093 nodeRange.selectNodeContents(node);
1097 range.collapse(true);
1099 nodeRange.collapse(true);
1101 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1102 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
1104 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1106 var nodeIsBefore = ss == 1;
1107 var nodeIsAfter = ee == -1;
1109 if (nodeIsBefore && nodeIsAfter) {
1112 if (!nodeIsBefore && nodeIsAfter) {
1113 return 1; //right trailed.
1116 if (nodeIsBefore && !nodeIsAfter) {
1117 return 2; // left trailed.
1123 cleanWordChars : function(input) {// change the chars to hex code
1126 [ 8211, "–" ],
1127 [ 8212, "—" ],
1136 Roo.each(swapCodes, function(sw) {
1137 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1139 output = output.replace(swapper, sw[1]);
1149 cleanUpChild : function (node)
1152 new Roo.htmleditor.FilterComment({node : node});
1153 new Roo.htmleditor.FilterAttributes({
1155 attrib_black : this.ablack,
1156 attrib_clean : this.aclean,
1157 style_white : this.cwhite,
1158 style_black : this.cblack
1160 new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
1161 new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
1167 * Clean up MS wordisms...
1168 * @deprecated - use filter directly
1170 cleanWord : function(node)
1172 new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
1179 * @deprecated - use filters
1181 cleanTableWidths : function(node)
1183 new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
1190 applyBlacklists : function()
1192 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1193 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1195 this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean : Roo.HtmlEditorCore.aclean;
1196 this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack : Roo.HtmlEditorCore.ablack;
1197 this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove : Roo.HtmlEditorCore.tag_remove;
1201 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1202 if (b.indexOf(tag) > -1) {
1205 this.white.push(tag);
1209 Roo.each(w, function(tag) {
1210 if (b.indexOf(tag) > -1) {
1213 if (this.white.indexOf(tag) > -1) {
1216 this.white.push(tag);
1221 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1222 if (w.indexOf(tag) > -1) {
1225 this.black.push(tag);
1229 Roo.each(b, function(tag) {
1230 if (w.indexOf(tag) > -1) {
1233 if (this.black.indexOf(tag) > -1) {
1236 this.black.push(tag);
1241 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1242 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1246 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1247 if (b.indexOf(tag) > -1) {
1250 this.cwhite.push(tag);
1254 Roo.each(w, function(tag) {
1255 if (b.indexOf(tag) > -1) {
1258 if (this.cwhite.indexOf(tag) > -1) {
1261 this.cwhite.push(tag);
1266 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1267 if (w.indexOf(tag) > -1) {
1270 this.cblack.push(tag);
1274 Roo.each(b, function(tag) {
1275 if (w.indexOf(tag) > -1) {
1278 if (this.cblack.indexOf(tag) > -1) {
1281 this.cblack.push(tag);
1286 setStylesheets : function(stylesheets)
1288 if(typeof(stylesheets) == 'string'){
1289 Roo.get(this.iframe.contentDocument.head).createChild({
1300 Roo.each(stylesheets, function(s) {
1305 Roo.get(_this.iframe.contentDocument.head).createChild({
1316 removeStylesheets : function()
1320 Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1325 setStyle : function(style)
1327 Roo.get(this.iframe.contentDocument.head).createChild({
1336 // hide stuff that is not compatible
1354 * @cfg {String} fieldClass @hide
1357 * @cfg {String} focusClass @hide
1360 * @cfg {String} autoCreate @hide
1363 * @cfg {String} inputType @hide
1366 * @cfg {String} invalidClass @hide
1369 * @cfg {String} invalidText @hide
1372 * @cfg {String} msgFx @hide
1375 * @cfg {String} validateOnBlur @hide
1379 Roo.HtmlEditorCore.white = [
1380 'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
1382 'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD', 'DIR', 'DIV',
1383 'DL', 'DT', 'H1', 'H2', 'H3', 'H4',
1384 'H5', 'H6', 'HR', 'ISINDEX', 'LISTING', 'MARQUEE',
1385 'MENU', 'MULTICOL', 'OL', 'P', 'PLAINTEXT', 'PRE',
1386 'TABLE', 'UL', 'XMP',
1388 'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH',
1391 'DIR', 'MENU', 'OL', 'UL', 'DL',
1397 Roo.HtmlEditorCore.black = [
1398 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1400 'BASE', 'BASEFONT', 'BGSOUND', 'BLINK', 'BODY',
1401 'FRAME', 'FRAMESET', 'HEAD', 'HTML', 'ILAYER',
1402 'IFRAME', 'LAYER', 'LINK', 'META', 'OBJECT',
1403 'SCRIPT', 'STYLE' ,'TITLE', 'XML',
1404 //'FONT' // CLEAN LATER..
1405 'COLGROUP', 'COL' // messy tables.
1408 Roo.HtmlEditorCore.clean = [ // ?? needed???
1409 'SCRIPT', 'STYLE', 'TITLE', 'XML'
1411 Roo.HtmlEditorCore.tag_remove = [
1416 Roo.HtmlEditorCore.ablack = [
1420 Roo.HtmlEditorCore.aclean = [
1421 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1425 Roo.HtmlEditorCore.pwhite= [
1426 'http', 'https', 'mailto'
1429 // white listed style attributes.
1430 Roo.HtmlEditorCore.cwhite= [
1431 // 'text-align', /// default is to allow most things..
1437 // black listed style attributes.
1438 Roo.HtmlEditorCore.cblack= [
1439 // 'font-size' -- this can be set by the project