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)
661 this.owner.fireEvent('editorevent', this, e);
662 // this.updateToolbar();
663 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
666 insertTag : function(tg)
668 // could be a bit smarter... -> wrap the current selected tRoo..
669 if (tg.toLowerCase() == 'span' ||
670 tg.toLowerCase() == 'code' ||
671 tg.toLowerCase() == 'sup' ||
672 tg.toLowerCase() == 'sub'
675 range = this.createRange(this.getSelection());
676 var wrappingNode = this.doc.createElement(tg.toLowerCase());
677 wrappingNode.appendChild(range.extractContents());
678 range.insertNode(wrappingNode);
685 this.execCmd("formatblock", tg);
686 this.undoManager.addEvent();
689 insertText : function(txt)
693 var range = this.createRange();
694 range.deleteContents();
695 //alert(Sender.getAttribute('label'));
697 range.insertNode(this.doc.createTextNode(txt));
698 this.undoManager.addEvent();
704 * Executes a Midas editor command on the editor document and performs necessary focus and
705 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
706 * @param {String} cmd The Midas command
707 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
709 relayCmd : function(cmd, value){
711 this.execCmd(cmd, value);
712 this.owner.fireEvent('editorevent', this);
713 //this.updateToolbar();
714 this.owner.deferFocus();
718 * Executes a Midas editor command directly on the editor document.
719 * For visual commands, you should use {@link #relayCmd} instead.
720 * <b>This should only be called after the editor is initialized.</b>
721 * @param {String} cmd The Midas command
722 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
724 execCmd : function(cmd, value){
725 this.doc.execCommand(cmd, false, value === undefined ? null : value);
732 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
734 * @param {String} text | dom node..
736 insertAtCursor : function(text)
743 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
747 // from jquery ui (MIT licenced)
751 if (win.getSelection && win.getSelection().getRangeAt) {
753 // delete the existing?
755 this.createRange(this.getSelection()).deleteContents();
756 range = win.getSelection().getRangeAt(0);
757 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
758 range.insertNode(node);
759 range = range.cloneRange();
760 range.collapse(false);
762 win.getSelection().removeAllRanges();
763 win.getSelection().addRange(range);
767 } else if (win.document.selection && win.document.selection.createRange) {
768 // no firefox support
769 var txt = typeof(text) == 'string' ? text : text.outerHTML;
770 win.document.selection.createRange().pasteHTML(txt);
773 // no firefox support
774 var txt = typeof(text) == 'string' ? text : text.outerHTML;
775 this.execCmd('InsertHTML', txt);
783 mozKeyPress : function(e){
785 var c = e.getCharCode(), cmd;
788 c = String.fromCharCode(c).toLowerCase();
802 // this.cleanUpPaste.defer(100, this);
818 fixKeys : function(){ // load time branching for fastest keydown performance
821 var k = e.getKey(), r;
824 r = this.doc.selection.createRange();
827 r.pasteHTML('    ');
834 r = this.doc.selection.createRange();
836 var target = r.parentElement();
837 if(!target || target.tagName.toLowerCase() != 'li'){
839 r.pasteHTML('<br/>');
845 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
846 // this.cleanUpPaste.defer(100, this);
852 }else if(Roo.isOpera){
858 this.execCmd('InsertHTML','    ');
861 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
862 // this.cleanUpPaste.defer(100, this);
867 }else if(Roo.isSafari){
873 this.execCmd('InsertText','\t');
877 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
878 // this.cleanUpPaste.defer(100, this);
886 getAllAncestors: function()
888 var p = this.getSelectedNode();
891 a.push(p); // push blank onto stack..
892 p = this.getParentElement();
896 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
900 a.push(this.doc.body);
907 getSelection : function()
910 return Roo.isIE ? this.doc.selection : this.win.getSelection();
914 * @param {DomElement} node the node to select
916 selectNode : function(node)
918 Roo.select('.roo-ed-selection', false, this.doc).removeClass('roo-ed-selection');
919 Roo.get(node).addClass('roo-ed-selection');
920 var nodeRange = node.ownerDocument.createRange();
922 nodeRange.selectNode(node);
924 nodeRange.selectNodeContents(node);
926 //nodeRange.collapse(true);
927 var s = this.win.getSelection();
929 s.addRange(nodeRange);
932 getSelectedNode: function()
934 // this may only work on Gecko!!!
936 // should we cache this!!!!
941 var range = this.createRange(this.getSelection()).cloneRange();
944 var parent = range.parentElement();
946 var testRange = range.duplicate();
947 testRange.moveToElementText(parent);
948 if (testRange.inRange(range)) {
951 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
954 parent = parent.parentElement;
959 // is ancestor a text element.
960 var ac = range.commonAncestorContainer;
961 if (ac.nodeType == 3) {
965 var ar = ac.childNodes;
968 var other_nodes = [];
969 var has_other_nodes = false;
970 for (var i=0;i<ar.length;i++) {
971 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
974 // fullly contained node.
976 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
981 // probably selected..
982 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
983 other_nodes.push(ar[i]);
987 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
992 has_other_nodes = true;
994 if (!nodes.length && other_nodes.length) {
997 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
1003 createRange: function(sel)
1005 // this has strange effects when using with
1006 // top toolbar - not sure if it's a great idea.
1007 //this.editor.contentWindow.focus();
1008 if (typeof sel != "undefined") {
1010 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
1012 return this.doc.createRange();
1015 return this.doc.createRange();
1018 getParentElement: function()
1021 this.assignDocWin();
1022 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
1024 var range = this.createRange(sel);
1027 var p = range.commonAncestorContainer;
1028 while (p.nodeType == 3) { // text node
1039 * Range intersection.. the hard stuff...
1043 * [ -- selected range --- ]
1047 * if end is before start or hits it. fail.
1048 * if start is after end or hits it fail.
1050 * if either hits (but other is outside. - then it's not
1056 // @see http://www.thismuchiknow.co.uk/?p=64.
1057 rangeIntersectsNode : function(range, node)
1059 var nodeRange = node.ownerDocument.createRange();
1061 nodeRange.selectNode(node);
1063 nodeRange.selectNodeContents(node);
1066 var rangeStartRange = range.cloneRange();
1067 rangeStartRange.collapse(true);
1069 var rangeEndRange = range.cloneRange();
1070 rangeEndRange.collapse(false);
1072 var nodeStartRange = nodeRange.cloneRange();
1073 nodeStartRange.collapse(true);
1075 var nodeEndRange = nodeRange.cloneRange();
1076 nodeEndRange.collapse(false);
1078 return rangeStartRange.compareBoundaryPoints(
1079 Range.START_TO_START, nodeEndRange) == -1 &&
1080 rangeEndRange.compareBoundaryPoints(
1081 Range.START_TO_START, nodeStartRange) == 1;
1085 rangeCompareNode : function(range, node)
1087 var nodeRange = node.ownerDocument.createRange();
1089 nodeRange.selectNode(node);
1091 nodeRange.selectNodeContents(node);
1095 range.collapse(true);
1097 nodeRange.collapse(true);
1099 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1100 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
1102 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1104 var nodeIsBefore = ss == 1;
1105 var nodeIsAfter = ee == -1;
1107 if (nodeIsBefore && nodeIsAfter) {
1110 if (!nodeIsBefore && nodeIsAfter) {
1111 return 1; //right trailed.
1114 if (nodeIsBefore && !nodeIsAfter) {
1115 return 2; // left trailed.
1121 cleanWordChars : function(input) {// change the chars to hex code
1124 [ 8211, "–" ],
1125 [ 8212, "—" ],
1134 Roo.each(swapCodes, function(sw) {
1135 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1137 output = output.replace(swapper, sw[1]);
1147 cleanUpChild : function (node)
1150 new Roo.htmleditor.FilterComment({node : node});
1151 new Roo.htmleditor.FilterAttributes({
1153 attrib_black : this.ablack,
1154 attrib_clean : this.aclean,
1155 style_white : this.cwhite,
1156 style_black : this.cblack
1158 new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
1159 new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
1165 * Clean up MS wordisms...
1166 * @deprecated - use filter directly
1168 cleanWord : function(node)
1170 new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
1177 * @deprecated - use filters
1179 cleanTableWidths : function(node)
1181 new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
1188 applyBlacklists : function()
1190 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1191 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1193 this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean : Roo.HtmlEditorCore.aclean;
1194 this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack : Roo.HtmlEditorCore.ablack;
1195 this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove : Roo.HtmlEditorCore.tag_remove;
1199 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1200 if (b.indexOf(tag) > -1) {
1203 this.white.push(tag);
1207 Roo.each(w, function(tag) {
1208 if (b.indexOf(tag) > -1) {
1211 if (this.white.indexOf(tag) > -1) {
1214 this.white.push(tag);
1219 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1220 if (w.indexOf(tag) > -1) {
1223 this.black.push(tag);
1227 Roo.each(b, function(tag) {
1228 if (w.indexOf(tag) > -1) {
1231 if (this.black.indexOf(tag) > -1) {
1234 this.black.push(tag);
1239 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1240 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1244 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1245 if (b.indexOf(tag) > -1) {
1248 this.cwhite.push(tag);
1252 Roo.each(w, function(tag) {
1253 if (b.indexOf(tag) > -1) {
1256 if (this.cwhite.indexOf(tag) > -1) {
1259 this.cwhite.push(tag);
1264 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1265 if (w.indexOf(tag) > -1) {
1268 this.cblack.push(tag);
1272 Roo.each(b, function(tag) {
1273 if (w.indexOf(tag) > -1) {
1276 if (this.cblack.indexOf(tag) > -1) {
1279 this.cblack.push(tag);
1284 setStylesheets : function(stylesheets)
1286 if(typeof(stylesheets) == 'string'){
1287 Roo.get(this.iframe.contentDocument.head).createChild({
1298 Roo.each(stylesheets, function(s) {
1303 Roo.get(_this.iframe.contentDocument.head).createChild({
1314 removeStylesheets : function()
1318 Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1323 setStyle : function(style)
1325 Roo.get(this.iframe.contentDocument.head).createChild({
1334 // hide stuff that is not compatible
1352 * @cfg {String} fieldClass @hide
1355 * @cfg {String} focusClass @hide
1358 * @cfg {String} autoCreate @hide
1361 * @cfg {String} inputType @hide
1364 * @cfg {String} invalidClass @hide
1367 * @cfg {String} invalidText @hide
1370 * @cfg {String} msgFx @hide
1373 * @cfg {String} validateOnBlur @hide
1377 Roo.HtmlEditorCore.white = [
1378 'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
1380 'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD', 'DIR', 'DIV',
1381 'DL', 'DT', 'H1', 'H2', 'H3', 'H4',
1382 'H5', 'H6', 'HR', 'ISINDEX', 'LISTING', 'MARQUEE',
1383 'MENU', 'MULTICOL', 'OL', 'P', 'PLAINTEXT', 'PRE',
1384 'TABLE', 'UL', 'XMP',
1386 'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH',
1389 'DIR', 'MENU', 'OL', 'UL', 'DL',
1395 Roo.HtmlEditorCore.black = [
1396 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1398 'BASE', 'BASEFONT', 'BGSOUND', 'BLINK', 'BODY',
1399 'FRAME', 'FRAMESET', 'HEAD', 'HTML', 'ILAYER',
1400 'IFRAME', 'LAYER', 'LINK', 'META', 'OBJECT',
1401 'SCRIPT', 'STYLE' ,'TITLE', 'XML',
1402 //'FONT' // CLEAN LATER..
1403 'COLGROUP', 'COL' // messy tables.
1406 Roo.HtmlEditorCore.clean = [ // ?? needed???
1407 'SCRIPT', 'STYLE', 'TITLE', 'XML'
1409 Roo.HtmlEditorCore.tag_remove = [
1414 Roo.HtmlEditorCore.ablack = [
1418 Roo.HtmlEditorCore.aclean = [
1419 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1423 Roo.HtmlEditorCore.pwhite= [
1424 'http', 'https', 'mailto'
1427 // white listed style attributes.
1428 Roo.HtmlEditorCore.cwhite= [
1429 // 'text-align', /// default is to allow most things..
1435 // black listed style attributes.
1436 Roo.HtmlEditorCore.cblack= [
1437 // 'font-size' -- this can be set by the project