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 // in theory this detects if the last element is not a br, then we try and do that.
666 // its so clicking in space at bottom triggers adding a br and moving the cursor.
668 e.target.nodeName == 'BODY' &&
669 e.type == "mouseup" &&
670 this.doc.body.lastChild
672 var lc = this.doc.body.lastChild;
673 while (lc.nodeType == 3 && lc.nodeValue == '') {
674 lc = lc.previousSibling;
676 if (lc.nodeType == 1 && lc.nodeName != 'BR') {
677 // if last element is <BR> - then dont do anything.
679 var ns = this.doc.createElement('br');
680 this.doc.body.appendChild(ns);
681 range = this.doc.createRange();
682 range.setStartAfter(ns);
683 range.collapse(true);
684 var sel = this.win.getSelection();
685 sel.removeAllRanges();
692 this.owner.fireEvent('editorevent', this, e);
693 // this.updateToolbar();
694 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
697 insertTag : function(tg)
699 // could be a bit smarter... -> wrap the current selected tRoo..
700 if (tg.toLowerCase() == 'span' ||
701 tg.toLowerCase() == 'code' ||
702 tg.toLowerCase() == 'sup' ||
703 tg.toLowerCase() == 'sub'
706 range = this.createRange(this.getSelection());
707 var wrappingNode = this.doc.createElement(tg.toLowerCase());
708 wrappingNode.appendChild(range.extractContents());
709 range.insertNode(wrappingNode);
716 this.execCmd("formatblock", tg);
717 this.undoManager.addEvent();
720 insertText : function(txt)
724 var range = this.createRange();
725 range.deleteContents();
726 //alert(Sender.getAttribute('label'));
728 range.insertNode(this.doc.createTextNode(txt));
729 this.undoManager.addEvent();
735 * Executes a Midas editor command on the editor document and performs necessary focus and
736 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
737 * @param {String} cmd The Midas command
738 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
740 relayCmd : function(cmd, value){
742 this.execCmd(cmd, value);
743 this.owner.fireEvent('editorevent', this);
744 //this.updateToolbar();
745 this.owner.deferFocus();
749 * Executes a Midas editor command directly on the editor document.
750 * For visual commands, you should use {@link #relayCmd} instead.
751 * <b>This should only be called after the editor is initialized.</b>
752 * @param {String} cmd The Midas command
753 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
755 execCmd : function(cmd, value){
756 this.doc.execCommand(cmd, false, value === undefined ? null : value);
763 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
765 * @param {String} text | dom node..
767 insertAtCursor : function(text)
774 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
778 // from jquery ui (MIT licenced)
782 if (win.getSelection && win.getSelection().getRangeAt) {
784 // delete the existing?
786 this.createRange(this.getSelection()).deleteContents();
787 range = win.getSelection().getRangeAt(0);
788 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
789 range.insertNode(node);
790 range = range.cloneRange();
791 range.collapse(false);
793 win.getSelection().removeAllRanges();
794 win.getSelection().addRange(range);
798 } else if (win.document.selection && win.document.selection.createRange) {
799 // no firefox support
800 var txt = typeof(text) == 'string' ? text : text.outerHTML;
801 win.document.selection.createRange().pasteHTML(txt);
804 // no firefox support
805 var txt = typeof(text) == 'string' ? text : text.outerHTML;
806 this.execCmd('InsertHTML', txt);
814 mozKeyPress : function(e){
816 var c = e.getCharCode(), cmd;
819 c = String.fromCharCode(c).toLowerCase();
833 // this.cleanUpPaste.defer(100, this);
849 fixKeys : function(){ // load time branching for fastest keydown performance
852 var k = e.getKey(), r;
855 r = this.doc.selection.createRange();
858 r.pasteHTML('    ');
865 r = this.doc.selection.createRange();
867 var target = r.parentElement();
868 if(!target || target.tagName.toLowerCase() != 'li'){
870 r.pasteHTML('<br/>');
876 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
877 // this.cleanUpPaste.defer(100, this);
883 }else if(Roo.isOpera){
889 this.execCmd('InsertHTML','    ');
892 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
893 // this.cleanUpPaste.defer(100, this);
898 }else if(Roo.isSafari){
904 this.execCmd('InsertText','\t');
908 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
909 // this.cleanUpPaste.defer(100, this);
917 getAllAncestors: function()
919 var p = this.getSelectedNode();
922 a.push(p); // push blank onto stack..
923 p = this.getParentElement();
927 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
931 a.push(this.doc.body);
938 getSelection : function()
941 return Roo.isIE ? this.doc.selection : this.win.getSelection();
945 * @param {DomElement} node the node to select
947 selectNode : function(node)
949 var nodeRange = node.ownerDocument.createRange();
951 nodeRange.selectNode(node);
953 nodeRange.selectNodeContents(node);
955 //nodeRange.collapse(true);
956 var s = this.win.getSelection();
958 s.addRange(nodeRange);
961 getSelectedNode: function()
963 // this may only work on Gecko!!!
965 // should we cache this!!!!
970 var range = this.createRange(this.getSelection()).cloneRange();
973 var parent = range.parentElement();
975 var testRange = range.duplicate();
976 testRange.moveToElementText(parent);
977 if (testRange.inRange(range)) {
980 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
983 parent = parent.parentElement;
988 // is ancestor a text element.
989 var ac = range.commonAncestorContainer;
990 if (ac.nodeType == 3) {
994 var ar = ac.childNodes;
997 var other_nodes = [];
998 var has_other_nodes = false;
999 for (var i=0;i<ar.length;i++) {
1000 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
1003 // fullly contained node.
1005 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
1010 // probably selected..
1011 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
1012 other_nodes.push(ar[i]);
1016 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
1021 has_other_nodes = true;
1023 if (!nodes.length && other_nodes.length) {
1026 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
1032 createRange: function(sel)
1034 // this has strange effects when using with
1035 // top toolbar - not sure if it's a great idea.
1036 //this.editor.contentWindow.focus();
1037 if (typeof sel != "undefined") {
1039 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
1041 return this.doc.createRange();
1044 return this.doc.createRange();
1047 getParentElement: function()
1050 this.assignDocWin();
1051 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
1053 var range = this.createRange(sel);
1056 var p = range.commonAncestorContainer;
1057 while (p.nodeType == 3) { // text node
1068 * Range intersection.. the hard stuff...
1072 * [ -- selected range --- ]
1076 * if end is before start or hits it. fail.
1077 * if start is after end or hits it fail.
1079 * if either hits (but other is outside. - then it's not
1085 // @see http://www.thismuchiknow.co.uk/?p=64.
1086 rangeIntersectsNode : function(range, node)
1088 var nodeRange = node.ownerDocument.createRange();
1090 nodeRange.selectNode(node);
1092 nodeRange.selectNodeContents(node);
1095 var rangeStartRange = range.cloneRange();
1096 rangeStartRange.collapse(true);
1098 var rangeEndRange = range.cloneRange();
1099 rangeEndRange.collapse(false);
1101 var nodeStartRange = nodeRange.cloneRange();
1102 nodeStartRange.collapse(true);
1104 var nodeEndRange = nodeRange.cloneRange();
1105 nodeEndRange.collapse(false);
1107 return rangeStartRange.compareBoundaryPoints(
1108 Range.START_TO_START, nodeEndRange) == -1 &&
1109 rangeEndRange.compareBoundaryPoints(
1110 Range.START_TO_START, nodeStartRange) == 1;
1114 rangeCompareNode : function(range, node)
1116 var nodeRange = node.ownerDocument.createRange();
1118 nodeRange.selectNode(node);
1120 nodeRange.selectNodeContents(node);
1124 range.collapse(true);
1126 nodeRange.collapse(true);
1128 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1129 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
1131 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1133 var nodeIsBefore = ss == 1;
1134 var nodeIsAfter = ee == -1;
1136 if (nodeIsBefore && nodeIsAfter) {
1139 if (!nodeIsBefore && nodeIsAfter) {
1140 return 1; //right trailed.
1143 if (nodeIsBefore && !nodeIsAfter) {
1144 return 2; // left trailed.
1150 cleanWordChars : function(input) {// change the chars to hex code
1153 [ 8211, "–" ],
1154 [ 8212, "—" ],
1163 Roo.each(swapCodes, function(sw) {
1164 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1166 output = output.replace(swapper, sw[1]);
1176 cleanUpChild : function (node)
1179 new Roo.htmleditor.FilterComment({node : node});
1180 new Roo.htmleditor.FilterAttributes({
1182 attrib_black : this.ablack,
1183 attrib_clean : this.aclean,
1184 style_white : this.cwhite,
1185 style_black : this.cblack
1187 new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
1188 new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
1194 * Clean up MS wordisms...
1195 * @deprecated - use filter directly
1197 cleanWord : function(node)
1199 new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
1206 * @deprecated - use filters
1208 cleanTableWidths : function(node)
1210 new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
1217 applyBlacklists : function()
1219 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1220 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1222 this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean : Roo.HtmlEditorCore.aclean;
1223 this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack : Roo.HtmlEditorCore.ablack;
1224 this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove : Roo.HtmlEditorCore.tag_remove;
1228 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1229 if (b.indexOf(tag) > -1) {
1232 this.white.push(tag);
1236 Roo.each(w, function(tag) {
1237 if (b.indexOf(tag) > -1) {
1240 if (this.white.indexOf(tag) > -1) {
1243 this.white.push(tag);
1248 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1249 if (w.indexOf(tag) > -1) {
1252 this.black.push(tag);
1256 Roo.each(b, function(tag) {
1257 if (w.indexOf(tag) > -1) {
1260 if (this.black.indexOf(tag) > -1) {
1263 this.black.push(tag);
1268 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1269 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1273 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1274 if (b.indexOf(tag) > -1) {
1277 this.cwhite.push(tag);
1281 Roo.each(w, function(tag) {
1282 if (b.indexOf(tag) > -1) {
1285 if (this.cwhite.indexOf(tag) > -1) {
1288 this.cwhite.push(tag);
1293 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1294 if (w.indexOf(tag) > -1) {
1297 this.cblack.push(tag);
1301 Roo.each(b, function(tag) {
1302 if (w.indexOf(tag) > -1) {
1305 if (this.cblack.indexOf(tag) > -1) {
1308 this.cblack.push(tag);
1313 setStylesheets : function(stylesheets)
1315 if(typeof(stylesheets) == 'string'){
1316 Roo.get(this.iframe.contentDocument.head).createChild({
1327 Roo.each(stylesheets, function(s) {
1332 Roo.get(_this.iframe.contentDocument.head).createChild({
1343 removeStylesheets : function()
1347 Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1352 setStyle : function(style)
1354 Roo.get(this.iframe.contentDocument.head).createChild({
1363 // hide stuff that is not compatible
1381 * @cfg {String} fieldClass @hide
1384 * @cfg {String} focusClass @hide
1387 * @cfg {String} autoCreate @hide
1390 * @cfg {String} inputType @hide
1393 * @cfg {String} invalidClass @hide
1396 * @cfg {String} invalidText @hide
1399 * @cfg {String} msgFx @hide
1402 * @cfg {String} validateOnBlur @hide
1406 Roo.HtmlEditorCore.white = [
1407 'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
1409 'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD', 'DIR', 'DIV',
1410 'DL', 'DT', 'H1', 'H2', 'H3', 'H4',
1411 'H5', 'H6', 'HR', 'ISINDEX', 'LISTING', 'MARQUEE',
1412 'MENU', 'MULTICOL', 'OL', 'P', 'PLAINTEXT', 'PRE',
1413 'TABLE', 'UL', 'XMP',
1415 'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH',
1418 'DIR', 'MENU', 'OL', 'UL', 'DL',
1424 Roo.HtmlEditorCore.black = [
1425 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1427 'BASE', 'BASEFONT', 'BGSOUND', 'BLINK', 'BODY',
1428 'FRAME', 'FRAMESET', 'HEAD', 'HTML', 'ILAYER',
1429 'IFRAME', 'LAYER', 'LINK', 'META', 'OBJECT',
1430 'SCRIPT', 'STYLE' ,'TITLE', 'XML',
1431 //'FONT' // CLEAN LATER..
1432 'COLGROUP', 'COL' // messy tables.
1435 Roo.HtmlEditorCore.clean = [ // ?? needed???
1436 'SCRIPT', 'STYLE', 'TITLE', 'XML'
1438 Roo.HtmlEditorCore.tag_remove = [
1443 Roo.HtmlEditorCore.ablack = [
1447 Roo.HtmlEditorCore.aclean = [
1448 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1452 Roo.HtmlEditorCore.pwhite= [
1453 'http', 'https', 'mailto'
1456 // white listed style attributes.
1457 Roo.HtmlEditorCore.cwhite= [
1458 // 'text-align', /// default is to allow most things..
1464 // black listed style attributes.
1465 Roo.HtmlEditorCore.cblack= [
1466 // 'font-size' -- this can be set by the project