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)
362 new Roo.htmleditor.FilterBlock({ node : div });
364 var html = div.innerHTML;
366 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
367 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
369 html = '<div style="'+m[0]+'">' + html + '</div>';
372 html = this.cleanHtml(html);
373 // fix up the special chars.. normaly like back quotes in word...
374 // however we do not want to do this with chinese..
375 html = html.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u0080-\uFFFF]/g, function(match) {
377 var cc = match.charCodeAt();
379 // Get the character value, handling surrogate pairs
380 if (match.length == 2) {
381 // It's a surrogate pair, calculate the Unicode code point
382 var high = match.charCodeAt(0) - 0xD800;
383 var low = match.charCodeAt(1) - 0xDC00;
384 cc = (high * 0x400) + low + 0x10000;
386 (cc >= 0x4E00 && cc < 0xA000 ) ||
387 (cc >= 0x3400 && cc < 0x4E00 ) ||
388 (cc >= 0xf900 && cc < 0xfb00 )
393 // No, use a numeric entity. Here we brazenly (and possibly mistakenly)
394 return "&#" + cc + ";";
401 if(this.owner.fireEvent('beforesync', this, html) !== false){
402 this.el.dom.value = html;
403 this.owner.fireEvent('sync', this, html);
409 * TEXTAREA -> EDITABLE
410 * Protected method that will not generally be called directly. Pushes the value of the textarea
411 * into the iframe editor.
413 pushValue : function()
415 //Roo.log("HtmlEditorCore:pushValue (TEXT->EDITOR)");
416 if(this.initialized){
417 var v = this.el.dom.value.trim();
420 if(this.owner.fireEvent('beforepush', this, v) !== false){
421 var d = (this.doc.body || this.doc.documentElement);
424 this.el.dom.value = d.innerHTML;
425 this.owner.fireEvent('push', this, v);
427 Roo.htmleditor.Block.initAll(this.doc.body);
429 var lc = this.doc.body.lastChild;
430 if (lc && lc.nodeType == 1 && lc.getAttribute("contenteditable") == "false") {
431 // add an extra line at the end.
432 this.doc.body.appendChild(this.doc.createElement('br'));
440 deferFocus : function(){
441 this.focus.defer(10, this);
446 if(this.win && !this.sourceEditMode){
453 assignDocWin: function()
455 var iframe = this.iframe;
458 this.doc = iframe.contentWindow.document;
459 this.win = iframe.contentWindow;
461 // if (!Roo.get(this.frameId)) {
464 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
465 // this.win = Roo.get(this.frameId).dom.contentWindow;
467 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
471 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
472 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
477 initEditor : function(){
478 //console.log("INIT EDITOR");
483 this.doc.designMode="on";
485 this.doc.write(this.getDocMarkup());
488 var dbody = (this.doc.body || this.doc.documentElement);
489 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
490 // this copies styles from the containing element into thsi one..
491 // not sure why we need all of this..
492 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
494 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
495 //ss['background-attachment'] = 'fixed'; // w3c
496 dbody.bgProperties = 'fixed'; // ie
497 //Roo.DomHelper.applyStyles(dbody, ss);
498 Roo.EventManager.on(this.doc, {
499 //'mousedown': this.onEditorEvent,
500 'mouseup': this.onEditorEvent,
501 'dblclick': this.onEditorEvent,
502 'click': this.onEditorEvent,
503 'keyup': this.onEditorEvent,
508 Roo.EventManager.on(this.doc, {
509 'paste': this.onPasteEvent,
513 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
515 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
516 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
518 this.initialized = true;
521 // initialize special key events - enter
522 new Roo.htmleditor.KeyEnter({core : this});
526 this.owner.fireEvent('initialize', this);
530 onPasteEvent : function(e,v)
532 // I think we better assume paste is going to be a dirty load of rubish from word..
534 // even pasting into a 'email version' of this widget will have to clean up that mess.
535 var cd = (e.browserEvent.clipboardData || window.clipboardData);
537 // check what type of paste - if it's an image, then handle it differently.
538 if (cd.files.length > 0) {
540 var urlAPI = (window.createObjectURL && window) ||
541 (window.URL && URL.revokeObjectURL && URL) ||
542 (window.webkitURL && webkitURL);
544 var url = urlAPI.createObjectURL( cd.files[0]);
545 this.insertAtCursor('<img src=" + url + ">');
549 var html = cd.getData('text/html'); // clipboard event
550 var parser = new Roo.rtf.Parser(cd.getData('text/rtf'));
551 var images = parser.doc ? parser.doc.getElementsByType('pict') : [];
555 images = images.filter(function(g) { return !g.path.match(/^rtf\/(head|pgdsctbl|listtable)/); }) // ignore headers
556 .map(function(g) { return g.toDataURL(); });
559 html = this.cleanWordChars(html);
561 var d = (new DOMParser().parseFromString(html, 'text/html')).body;
564 var sn = this.getParentElement();
565 // check if d contains a table, and prevent nesting??
566 //Roo.log(d.getElementsByTagName('table'));
568 //Roo.log(sn.closest('table'));
569 if (d.getElementsByTagName('table').length && sn && sn.closest('table')) {
571 this.insertAtCursor("You can not nest tables");
572 //Roo.log("prevent?"); // fixme -
576 if (images.length > 0) {
577 Roo.each(d.getElementsByTagName('img'), function(img, i) {
578 img.setAttribute('src', images[i]);
583 new Roo.htmleditor.FilterStyleToTag({ node : d });
584 new Roo.htmleditor.FilterAttributes({
586 attrib_white : ['href', 'src', 'name', 'align'],
587 attrib_clean : ['href', 'src' ]
589 new Roo.htmleditor.FilterBlack({ node : d, tag : this.black});
591 new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT' ]} );
592 new Roo.htmleditor.FilterParagraph({ node : d });
593 new Roo.htmleditor.FilterSpan({ node : d });
594 new Roo.htmleditor.FilterLongBr({ node : d });
598 this.insertAtCursor(d.innerHTML);
599 Roo.htmleditor.Block.initAll(this.doc.body);
604 // default behaveiour should be our local cleanup paste? (optional?)
605 // for simple editor - we want to hammer the paste and get rid of everything... - so over-rideable..
606 //this.owner.fireEvent('paste', e, v);
609 onDestroy : function(){
615 //for (var i =0; i < this.toolbars.length;i++) {
616 // // fixme - ask toolbars for heights?
617 // this.toolbars[i].onDestroy();
620 //this.wrap.dom.innerHTML = '';
621 //this.wrap.remove();
626 onFirstFocus : function(){
629 this.undoManager = new Roo.lib.UndoManager(100,(this.doc.body || this.doc.documentElement));
631 this.activated = true;
634 if(Roo.isGecko){ // prevent silly gecko errors
636 var s = this.win.getSelection();
637 if(!s.focusNode || s.focusNode.nodeType != 3){
638 var r = s.getRangeAt(0);
639 r.selectNodeContents((this.doc.body || this.doc.documentElement));
644 this.execCmd('useCSS', true);
645 this.execCmd('styleWithCSS', false);
648 this.owner.fireEvent('activate', this);
652 adjustFont: function(btn){
653 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
654 //if(Roo.isSafari){ // safari
657 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
658 if(Roo.isSafari){ // safari
659 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
660 v = (v < 10) ? 10 : v;
661 v = (v > 48) ? 48 : v;
662 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
667 v = Math.max(1, v+adjust);
669 this.execCmd('FontSize', v );
672 onEditorEvent : function(e)
675 if (e && (e.ctrlKey || e.metaKey) && e.keyCode === 90) {
676 return; // we do not handle this.. (undo manager does..)
678 // in theory this detects if the last element is not a br, then we try and do that.
679 // its so clicking in space at bottom triggers adding a br and moving the cursor.
681 e.target.nodeName == 'BODY' &&
682 e.type == "mouseup" &&
683 this.doc.body.lastChild
685 var lc = this.doc.body.lastChild;
686 while (lc.nodeType == 3 && lc.nodeValue == '') {
687 lc = lc.previousSibling;
689 if (lc.nodeType == 1 && lc.nodeName != 'BR') {
690 // if last element is <BR> - then dont do anything.
692 var ns = this.doc.createElement('br');
693 this.doc.body.appendChild(ns);
694 range = this.doc.createRange();
695 range.setStartAfter(ns);
696 range.collapse(true);
697 var sel = this.win.getSelection();
698 sel.removeAllRanges();
705 this.owner.fireEvent('editorevent', this, e);
706 // this.updateToolbar();
707 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
710 insertTag : function(tg)
712 // could be a bit smarter... -> wrap the current selected tRoo..
713 if (tg.toLowerCase() == 'span' ||
714 tg.toLowerCase() == 'code' ||
715 tg.toLowerCase() == 'sup' ||
716 tg.toLowerCase() == 'sub'
719 range = this.createRange(this.getSelection());
720 var wrappingNode = this.doc.createElement(tg.toLowerCase());
721 wrappingNode.appendChild(range.extractContents());
722 range.insertNode(wrappingNode);
729 this.execCmd("formatblock", tg);
730 this.undoManager.addEvent();
733 insertText : function(txt)
737 var range = this.createRange();
738 range.deleteContents();
739 //alert(Sender.getAttribute('label'));
741 range.insertNode(this.doc.createTextNode(txt));
742 this.undoManager.addEvent();
748 * Executes a Midas editor command on the editor document and performs necessary focus and
749 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
750 * @param {String} cmd The Midas command
751 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
753 relayCmd : function(cmd, value){
755 this.execCmd(cmd, value);
756 this.owner.fireEvent('editorevent', this);
757 //this.updateToolbar();
758 this.owner.deferFocus();
762 * Executes a Midas editor command directly on the editor document.
763 * For visual commands, you should use {@link #relayCmd} instead.
764 * <b>This should only be called after the editor is initialized.</b>
765 * @param {String} cmd The Midas command
766 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
768 execCmd : function(cmd, value){
769 this.doc.execCommand(cmd, false, value === undefined ? null : value);
776 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
778 * @param {String} text | dom node..
780 insertAtCursor : function(text)
787 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
791 // from jquery ui (MIT licenced)
795 if (win.getSelection && win.getSelection().getRangeAt) {
797 // delete the existing?
799 this.createRange(this.getSelection()).deleteContents();
800 range = win.getSelection().getRangeAt(0);
801 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
802 range.insertNode(node);
803 range = range.cloneRange();
804 range.collapse(false);
806 win.getSelection().removeAllRanges();
807 win.getSelection().addRange(range);
811 } else if (win.document.selection && win.document.selection.createRange) {
812 // no firefox support
813 var txt = typeof(text) == 'string' ? text : text.outerHTML;
814 win.document.selection.createRange().pasteHTML(txt);
817 // no firefox support
818 var txt = typeof(text) == 'string' ? text : text.outerHTML;
819 this.execCmd('InsertHTML', txt);
827 mozKeyPress : function(e){
829 var c = e.getCharCode(), cmd;
832 c = String.fromCharCode(c).toLowerCase();
846 // this.cleanUpPaste.defer(100, this);
862 fixKeys : function(){ // load time branching for fastest keydown performance
865 var k = e.getKey(), r;
868 r = this.doc.selection.createRange();
871 r.pasteHTML('    ');
876 /// this is handled by Roo.htmleditor.KeyEnter
879 r = this.doc.selection.createRange();
881 var target = r.parentElement();
882 if(!target || target.tagName.toLowerCase() != 'li'){
884 r.pasteHTML('<br/>');
891 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
892 // this.cleanUpPaste.defer(100, this);
898 }else if(Roo.isOpera){
904 this.execCmd('InsertHTML','    ');
907 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
908 // this.cleanUpPaste.defer(100, this);
913 }else if(Roo.isSafari){
919 this.execCmd('InsertText','\t');
923 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
924 // this.cleanUpPaste.defer(100, this);
932 getAllAncestors: function()
934 var p = this.getSelectedNode();
937 a.push(p); // push blank onto stack..
938 p = this.getParentElement();
942 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
946 a.push(this.doc.body);
953 getSelection : function()
956 return Roo.isIE ? this.doc.selection : this.win.getSelection();
960 * @param {DomElement} node the node to select
962 selectNode : function(node)
964 var nodeRange = node.ownerDocument.createRange();
966 nodeRange.selectNode(node);
968 nodeRange.selectNodeContents(node);
970 //nodeRange.collapse(true);
971 var s = this.win.getSelection();
973 s.addRange(nodeRange);
976 getSelectedNode: function()
978 // this may only work on Gecko!!!
980 // should we cache this!!!!
985 var range = this.createRange(this.getSelection()).cloneRange();
988 var parent = range.parentElement();
990 var testRange = range.duplicate();
991 testRange.moveToElementText(parent);
992 if (testRange.inRange(range)) {
995 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
998 parent = parent.parentElement;
1003 // is ancestor a text element.
1004 var ac = range.commonAncestorContainer;
1005 if (ac.nodeType == 3) {
1009 var ar = ac.childNodes;
1012 var other_nodes = [];
1013 var has_other_nodes = false;
1014 for (var i=0;i<ar.length;i++) {
1015 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
1018 // fullly contained node.
1020 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
1025 // probably selected..
1026 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
1027 other_nodes.push(ar[i]);
1031 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
1036 has_other_nodes = true;
1038 if (!nodes.length && other_nodes.length) {
1041 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
1047 createRange: function(sel)
1049 // this has strange effects when using with
1050 // top toolbar - not sure if it's a great idea.
1051 //this.editor.contentWindow.focus();
1052 if (typeof sel != "undefined") {
1054 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
1056 return this.doc.createRange();
1059 return this.doc.createRange();
1062 getParentElement: function()
1065 this.assignDocWin();
1066 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
1068 var range = this.createRange(sel);
1071 var p = range.commonAncestorContainer;
1072 while (p.nodeType == 3) { // text node
1083 * Range intersection.. the hard stuff...
1087 * [ -- selected range --- ]
1091 * if end is before start or hits it. fail.
1092 * if start is after end or hits it fail.
1094 * if either hits (but other is outside. - then it's not
1100 // @see http://www.thismuchiknow.co.uk/?p=64.
1101 rangeIntersectsNode : function(range, node)
1103 var nodeRange = node.ownerDocument.createRange();
1105 nodeRange.selectNode(node);
1107 nodeRange.selectNodeContents(node);
1110 var rangeStartRange = range.cloneRange();
1111 rangeStartRange.collapse(true);
1113 var rangeEndRange = range.cloneRange();
1114 rangeEndRange.collapse(false);
1116 var nodeStartRange = nodeRange.cloneRange();
1117 nodeStartRange.collapse(true);
1119 var nodeEndRange = nodeRange.cloneRange();
1120 nodeEndRange.collapse(false);
1122 return rangeStartRange.compareBoundaryPoints(
1123 Range.START_TO_START, nodeEndRange) == -1 &&
1124 rangeEndRange.compareBoundaryPoints(
1125 Range.START_TO_START, nodeStartRange) == 1;
1129 rangeCompareNode : function(range, node)
1131 var nodeRange = node.ownerDocument.createRange();
1133 nodeRange.selectNode(node);
1135 nodeRange.selectNodeContents(node);
1139 range.collapse(true);
1141 nodeRange.collapse(true);
1143 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1144 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
1146 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1148 var nodeIsBefore = ss == 1;
1149 var nodeIsAfter = ee == -1;
1151 if (nodeIsBefore && nodeIsAfter) {
1154 if (!nodeIsBefore && nodeIsAfter) {
1155 return 1; //right trailed.
1158 if (nodeIsBefore && !nodeIsAfter) {
1159 return 2; // left trailed.
1165 cleanWordChars : function(input) {// change the chars to hex code
1168 [ 8211, "–" ],
1169 [ 8212, "—" ],
1178 Roo.each(swapCodes, function(sw) {
1179 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1181 output = output.replace(swapper, sw[1]);
1191 cleanUpChild : function (node)
1194 new Roo.htmleditor.FilterComment({node : node});
1195 new Roo.htmleditor.FilterAttributes({
1197 attrib_black : this.ablack,
1198 attrib_clean : this.aclean,
1199 style_white : this.cwhite,
1200 style_black : this.cblack
1202 new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
1203 new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
1209 * Clean up MS wordisms...
1210 * @deprecated - use filter directly
1212 cleanWord : function(node)
1214 new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
1221 * @deprecated - use filters
1223 cleanTableWidths : function(node)
1225 new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
1232 applyBlacklists : function()
1234 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1235 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1237 this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean : Roo.HtmlEditorCore.aclean;
1238 this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack : Roo.HtmlEditorCore.ablack;
1239 this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove : Roo.HtmlEditorCore.tag_remove;
1243 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1244 if (b.indexOf(tag) > -1) {
1247 this.white.push(tag);
1251 Roo.each(w, function(tag) {
1252 if (b.indexOf(tag) > -1) {
1255 if (this.white.indexOf(tag) > -1) {
1258 this.white.push(tag);
1263 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1264 if (w.indexOf(tag) > -1) {
1267 this.black.push(tag);
1271 Roo.each(b, function(tag) {
1272 if (w.indexOf(tag) > -1) {
1275 if (this.black.indexOf(tag) > -1) {
1278 this.black.push(tag);
1283 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1284 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1288 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1289 if (b.indexOf(tag) > -1) {
1292 this.cwhite.push(tag);
1296 Roo.each(w, function(tag) {
1297 if (b.indexOf(tag) > -1) {
1300 if (this.cwhite.indexOf(tag) > -1) {
1303 this.cwhite.push(tag);
1308 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1309 if (w.indexOf(tag) > -1) {
1312 this.cblack.push(tag);
1316 Roo.each(b, function(tag) {
1317 if (w.indexOf(tag) > -1) {
1320 if (this.cblack.indexOf(tag) > -1) {
1323 this.cblack.push(tag);
1328 setStylesheets : function(stylesheets)
1330 if(typeof(stylesheets) == 'string'){
1331 Roo.get(this.iframe.contentDocument.head).createChild({
1342 Roo.each(stylesheets, function(s) {
1347 Roo.get(_this.iframe.contentDocument.head).createChild({
1358 removeStylesheets : function()
1362 Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1367 setStyle : function(style)
1369 Roo.get(this.iframe.contentDocument.head).createChild({
1378 // hide stuff that is not compatible
1396 * @cfg {String} fieldClass @hide
1399 * @cfg {String} focusClass @hide
1402 * @cfg {String} autoCreate @hide
1405 * @cfg {String} inputType @hide
1408 * @cfg {String} invalidClass @hide
1411 * @cfg {String} invalidText @hide
1414 * @cfg {String} msgFx @hide
1417 * @cfg {String} validateOnBlur @hide
1421 Roo.HtmlEditorCore.white = [
1422 'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
1424 'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD', 'DIR', 'DIV',
1425 'DL', 'DT', 'H1', 'H2', 'H3', 'H4',
1426 'H5', 'H6', 'HR', 'ISINDEX', 'LISTING', 'MARQUEE',
1427 'MENU', 'MULTICOL', 'OL', 'P', 'PLAINTEXT', 'PRE',
1428 'TABLE', 'UL', 'XMP',
1430 'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH',
1433 'DIR', 'MENU', 'OL', 'UL', 'DL',
1439 Roo.HtmlEditorCore.black = [
1440 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1442 'BASE', 'BASEFONT', 'BGSOUND', 'BLINK', 'BODY',
1443 'FRAME', 'FRAMESET', 'HEAD', 'HTML', 'ILAYER',
1444 'IFRAME', 'LAYER', 'LINK', 'META', 'OBJECT',
1445 'SCRIPT', 'STYLE' ,'TITLE', 'XML',
1446 //'FONT' // CLEAN LATER..
1447 'COLGROUP', 'COL' // messy tables.
1450 Roo.HtmlEditorCore.clean = [ // ?? needed???
1451 'SCRIPT', 'STYLE', 'TITLE', 'XML'
1453 Roo.HtmlEditorCore.tag_remove = [
1458 Roo.HtmlEditorCore.ablack = [
1462 Roo.HtmlEditorCore.aclean = [
1463 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1467 Roo.HtmlEditorCore.pwhite= [
1468 'http', 'https', 'mailto'
1471 // white listed style attributes.
1472 Roo.HtmlEditorCore.cwhite= [
1473 // 'text-align', /// default is to allow most things..
1479 // black listed style attributes.
1480 Roo.HtmlEditorCore.cblack= [
1481 // 'font-size' -- this can be set by the project