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)
111 * @cfg {boolean} autoClean - default true - loading and saving will remove quite a bit of formating,
112 * if you are doing an email editor, this probably needs disabling, it's designed
117 * @cfg {boolean} enableBlocks - default true - if the block editor (table and figure should be enabled)
121 * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
127 * @cfg {boolean} allowComments - default false - allow comments in HTML source
128 * - by default they are stripped - if you are editing email you may need this.
130 allowComments: false,
134 // private properties
135 validationEvent : false,
139 sourceEditMode : false,
140 onFocus : Roo.emptyFn,
146 // blacklist + whitelisted elements..
155 * Protected method that will not generally be called directly. It
156 * is called when the editor initializes the iframe with HTML contents. Override this method if you
157 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
159 getDocMarkup : function(){
163 // inherit styels from page...??
164 if (this.stylesheets === false) {
166 Roo.get(document.head).select('style').each(function(node) {
167 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
170 Roo.get(document.head).select('link').each(function(node) {
171 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
174 } else if (!this.stylesheets.length) {
176 st = '<style type="text/css">' +
177 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
180 for (var i in this.stylesheets) {
181 if (typeof(this.stylesheets[i]) != 'string') {
184 st += '<link rel="stylesheet" href="' + this.stylesheets[i] +'" type="text/css">';
189 st += '<style type="text/css">' +
190 'IMG { cursor: pointer } ' +
193 var cls = 'roo-htmleditor-body';
195 if(this.bodyCls.length){
196 cls += ' ' + this.bodyCls;
199 return '<html><head>' + st +
200 //<style type="text/css">' +
201 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
203 ' </head><body contenteditable="true" data-enable-grammerly="true" class="' + cls + '"></body></html>';
207 onRender : function(ct, position)
210 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
211 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
214 this.el.dom.style.border = '0 none';
215 this.el.dom.setAttribute('tabIndex', -1);
216 this.el.addClass('x-hidden hide');
220 if(Roo.isIE){ // fix IE 1px bogus margin
221 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
225 this.frameId = Roo.id();
229 var iframe = this.owner.wrap.createChild({
231 cls: 'form-control', // bootstrap..
235 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
240 this.iframe = iframe.dom;
244 this.doc.designMode = 'on';
247 this.doc.write(this.getDocMarkup());
251 var task = { // must defer to wait for browser to be ready
253 //console.log("run task?" + this.doc.readyState);
255 if(this.doc.body || this.doc.readyState == 'complete'){
257 this.doc.designMode="on";
262 Roo.TaskMgr.stop(task);
263 this.initEditor.defer(10, this);
270 Roo.TaskMgr.start(task);
275 onResize : function(w, h)
277 Roo.log('resize: ' +w + ',' + h );
278 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
282 if(typeof w == 'number'){
284 this.iframe.style.width = w + 'px';
286 if(typeof h == 'number'){
288 this.iframe.style.height = h + 'px';
290 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
297 * Toggles the editor between standard and source edit mode.
298 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
300 toggleSourceEdit : function(sourceEditMode){
302 this.sourceEditMode = sourceEditMode === true;
304 if(this.sourceEditMode){
306 Roo.get(this.iframe).addClass(['x-hidden','hide', 'd-none']); //FIXME - what's the BS styles for these
309 Roo.get(this.iframe).removeClass(['x-hidden','hide', 'd-none']);
310 //this.iframe.className = '';
313 //this.setSize(this.owner.wrap.getSize());
314 //this.fireEvent('editmodechange', this, this.sourceEditMode);
321 * Protected method that will not generally be called directly. If you need/want
322 * custom HTML cleanup, this is the method you should override.
323 * @param {String} html The HTML to be cleaned
324 * return {String} The cleaned HTML
326 cleanHtml : function(html){
329 if(Roo.isSafari){ // strip safari nonsense
330 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
333 if(html == ' '){
340 * HTML Editor -> Textarea
341 * Protected method that will not generally be called directly. Syncs the contents
342 * of the editor iframe with the textarea.
344 syncValue : function()
346 //Roo.log("HtmlEditorCore:syncValue (EDITOR->TEXT)");
347 if(this.initialized){
349 this.undoManager.addEvent();
352 var bd = (this.doc.body || this.doc.documentElement);
356 var div = document.createElement('div');
357 div.innerHTML = bd.innerHTML;
360 if (this.enableBlocks) {
361 new Roo.htmleditor.FilterBlock({ node : div });
366 var html = div.innerHTML;
368 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
369 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
371 html = '<div style="'+m[0]+'">' + html + '</div>';
374 html = this.cleanHtml(html);
375 // fix up the special chars.. normaly like back quotes in word...
376 // however we do not want to do this with chinese..
377 html = html.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u0080-\uFFFF]/g, function(match) {
379 var cc = match.charCodeAt();
381 // Get the character value, handling surrogate pairs
382 if (match.length == 2) {
383 // It's a surrogate pair, calculate the Unicode code point
384 var high = match.charCodeAt(0) - 0xD800;
385 var low = match.charCodeAt(1) - 0xDC00;
386 cc = (high * 0x400) + low + 0x10000;
388 (cc >= 0x4E00 && cc < 0xA000 ) ||
389 (cc >= 0x3400 && cc < 0x4E00 ) ||
390 (cc >= 0xf900 && cc < 0xfb00 )
395 // No, use a numeric entity. Here we brazenly (and possibly mistakenly)
396 return "&#" + cc + ";";
403 if(this.owner.fireEvent('beforesync', this, html) !== false){
404 this.el.dom.value = html;
405 this.owner.fireEvent('sync', this, html);
411 * TEXTAREA -> EDITABLE
412 * Protected method that will not generally be called directly. Pushes the value of the textarea
413 * into the iframe editor.
415 pushValue : function()
417 //Roo.log("HtmlEditorCore:pushValue (TEXT->EDITOR)");
418 if(this.initialized){
419 var v = this.el.dom.value.trim();
422 if(this.owner.fireEvent('beforepush', this, v) !== false){
423 var d = (this.doc.body || this.doc.documentElement);
426 this.el.dom.value = d.innerHTML;
427 this.owner.fireEvent('push', this, v);
429 Roo.htmleditor.Block.initAll(this.doc.body);
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);
518 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
519 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
521 this.initialized = true;
524 // initialize special key events - enter
525 new Roo.htmleditor.KeyEnter({core : this});
529 this.owner.fireEvent('initialize', this);
533 onPasteEvent : function(e,v)
535 // I think we better assume paste is going to be a dirty load of rubish from word..
537 // even pasting into a 'email version' of this widget will have to clean up that mess.
538 var cd = (e.browserEvent.clipboardData || window.clipboardData);
540 // check what type of paste - if it's an image, then handle it differently.
541 if (cd.files.length > 0) {
543 var urlAPI = (window.createObjectURL && window) ||
544 (window.URL && URL.revokeObjectURL && URL) ||
545 (window.webkitURL && webkitURL);
547 var url = urlAPI.createObjectURL( cd.files[0]);
548 this.insertAtCursor('<img src=" + url + ">');
552 var html = cd.getData('text/html'); // clipboard event
553 var parser = new Roo.rtf.Parser(cd.getData('text/rtf'));
554 var images = parser.doc ? parser.doc.getElementsByType('pict') : [];
558 images = images.filter(function(g) { return !g.path.match(/^rtf\/(head|pgdsctbl|listtable)/); }) // ignore headers
559 .map(function(g) { return g.toDataURL(); });
562 html = this.cleanWordChars(html);
564 var d = (new DOMParser().parseFromString(html, 'text/html')).body;
567 var sn = this.getParentElement();
568 // check if d contains a table, and prevent nesting??
569 //Roo.log(d.getElementsByTagName('table'));
571 //Roo.log(sn.closest('table'));
572 if (d.getElementsByTagName('table').length && sn && sn.closest('table')) {
574 this.insertAtCursor("You can not nest tables");
575 //Roo.log("prevent?"); // fixme -
579 if (images.length > 0) {
580 Roo.each(d.getElementsByTagName('img'), function(img, i) {
581 img.setAttribute('src', images[i]);
586 new Roo.htmleditor.FilterStyleToTag({ node : d });
587 new Roo.htmleditor.FilterAttributes({
589 attrib_white : ['href', 'src', 'name', 'align'],
590 attrib_clean : ['href', 'src' ]
592 new Roo.htmleditor.FilterBlack({ node : d, tag : this.black});
594 new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT' ]} );
595 new Roo.htmleditor.FilterParagraph({ node : d });
596 new Roo.htmleditor.FilterSpan({ node : d });
597 new Roo.htmleditor.FilterLongBr({ node : d });
601 this.insertAtCursor(d.innerHTML);
602 Roo.htmleditor.Block.initAll(this.doc.body);
607 // default behaveiour should be our local cleanup paste? (optional?)
608 // for simple editor - we want to hammer the paste and get rid of everything... - so over-rideable..
609 //this.owner.fireEvent('paste', e, v);
612 onDestroy : function(){
618 //for (var i =0; i < this.toolbars.length;i++) {
619 // // fixme - ask toolbars for heights?
620 // this.toolbars[i].onDestroy();
623 //this.wrap.dom.innerHTML = '';
624 //this.wrap.remove();
629 onFirstFocus : function(){
632 this.undoManager = new Roo.lib.UndoManager(100,(this.doc.body || this.doc.documentElement));
634 this.activated = true;
637 if(Roo.isGecko){ // prevent silly gecko errors
639 var s = this.win.getSelection();
640 if(!s.focusNode || s.focusNode.nodeType != 3){
641 var r = s.getRangeAt(0);
642 r.selectNodeContents((this.doc.body || this.doc.documentElement));
647 this.execCmd('useCSS', true);
648 this.execCmd('styleWithCSS', false);
651 this.owner.fireEvent('activate', this);
655 adjustFont: function(btn){
656 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
657 //if(Roo.isSafari){ // safari
660 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
661 if(Roo.isSafari){ // safari
662 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
663 v = (v < 10) ? 10 : v;
664 v = (v > 48) ? 48 : v;
665 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
670 v = Math.max(1, v+adjust);
672 this.execCmd('FontSize', v );
675 onEditorEvent : function(e)
678 if (e && (e.ctrlKey || e.metaKey) && e.keyCode === 90) {
679 return; // we do not handle this.. (undo manager does..)
681 // in theory this detects if the last element is not a br, then we try and do that.
682 // its so clicking in space at bottom triggers adding a br and moving the cursor.
684 e.target.nodeName == 'BODY' &&
685 e.type == "mouseup" &&
686 this.doc.body.lastChild
688 var lc = this.doc.body.lastChild;
689 // gtx-trans is google translate plugin adding crap.
690 while ((lc.nodeType == 3 && lc.nodeValue == '') || lc.id == 'gtx-trans') {
691 lc = lc.previousSibling;
693 if (lc.nodeType == 1 && lc.nodeName != 'BR') {
694 // if last element is <BR> - then dont do anything.
696 var ns = this.doc.createElement('br');
697 this.doc.body.appendChild(ns);
698 range = this.doc.createRange();
699 range.setStartAfter(ns);
700 range.collapse(true);
701 var sel = this.win.getSelection();
702 sel.removeAllRanges();
709 this.fireEditorEvent(e);
710 // this.updateToolbar();
711 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
714 fireEditorEvent: function(e)
716 this.owner.fireEvent('editorevent', this, e);
719 insertTag : function(tg)
721 // could be a bit smarter... -> wrap the current selected tRoo..
722 if (tg.toLowerCase() == 'span' ||
723 tg.toLowerCase() == 'code' ||
724 tg.toLowerCase() == 'sup' ||
725 tg.toLowerCase() == 'sub'
728 range = this.createRange(this.getSelection());
729 var wrappingNode = this.doc.createElement(tg.toLowerCase());
730 wrappingNode.appendChild(range.extractContents());
731 range.insertNode(wrappingNode);
738 this.execCmd("formatblock", tg);
739 this.undoManager.addEvent();
742 insertText : function(txt)
746 var range = this.createRange();
747 range.deleteContents();
748 //alert(Sender.getAttribute('label'));
750 range.insertNode(this.doc.createTextNode(txt));
751 this.undoManager.addEvent();
757 * Executes a Midas editor command on the editor document and performs necessary focus and
758 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
759 * @param {String} cmd The Midas command
760 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
762 relayCmd : function(cmd, value)
768 case 'justifycenter':
769 // if we are in a cell, then we will adjust the
770 var n = this.getParentElement();
771 var td = n.closest('td');
773 var bl = Roo.htmleditor.Block.factory(td);
774 bl.textAlign = cmd.replace('justify','');
776 this.owner.fireEvent('editorevent', this);
779 this.execCmd('styleWithCSS', true); //
783 // if there is no selection, then we insert, and set the curson inside it..
784 this.execCmd('styleWithCSS', false);
794 this.execCmd(cmd, value);
795 this.owner.fireEvent('editorevent', this);
796 //this.updateToolbar();
797 this.owner.deferFocus();
801 * Executes a Midas editor command directly on the editor document.
802 * For visual commands, you should use {@link #relayCmd} instead.
803 * <b>This should only be called after the editor is initialized.</b>
804 * @param {String} cmd The Midas command
805 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
807 execCmd : function(cmd, value){
808 this.doc.execCommand(cmd, false, value === undefined ? null : value);
815 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
817 * @param {String} text | dom node..
819 insertAtCursor : function(text)
826 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
830 // from jquery ui (MIT licenced)
834 if (win.getSelection && win.getSelection().getRangeAt) {
836 // delete the existing?
838 this.createRange(this.getSelection()).deleteContents();
839 range = win.getSelection().getRangeAt(0);
840 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
841 range.insertNode(node);
842 range = range.cloneRange();
843 range.collapse(false);
845 win.getSelection().removeAllRanges();
846 win.getSelection().addRange(range);
850 } else if (win.document.selection && win.document.selection.createRange) {
851 // no firefox support
852 var txt = typeof(text) == 'string' ? text : text.outerHTML;
853 win.document.selection.createRange().pasteHTML(txt);
856 // no firefox support
857 var txt = typeof(text) == 'string' ? text : text.outerHTML;
858 this.execCmd('InsertHTML', txt);
866 mozKeyPress : function(e){
868 var c = e.getCharCode(), cmd;
871 c = String.fromCharCode(c).toLowerCase();
885 // this.cleanUpPaste.defer(100, this);
903 fixKeys : function(){ // load time branching for fastest keydown performance
908 var k = e.getKey(), r;
911 r = this.doc.selection.createRange();
914 r.pasteHTML('    ');
919 /// this is handled by Roo.htmleditor.KeyEnter
922 r = this.doc.selection.createRange();
924 var target = r.parentElement();
925 if(!target || target.tagName.toLowerCase() != 'li'){
927 r.pasteHTML('<br/>');
934 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
935 // this.cleanUpPaste.defer(100, this);
941 }else if(Roo.isOpera){
947 this.execCmd('InsertHTML','    ');
951 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
952 // this.cleanUpPaste.defer(100, this);
957 }else if(Roo.isSafari){
963 this.execCmd('InsertText','\t');
969 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
970 // this.cleanUpPaste.defer(100, this);
978 getAllAncestors: function()
980 var p = this.getSelectedNode();
983 a.push(p); // push blank onto stack..
984 p = this.getParentElement();
988 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
992 a.push(this.doc.body);
999 getSelection : function()
1001 this.assignDocWin();
1002 return Roo.lib.Selection.wrap(Roo.isIE ? this.doc.selection : this.win.getSelection(), this.doc);
1006 * @param {DomElement} node the node to select
1008 selectNode : function(node, collapse)
1010 var nodeRange = node.ownerDocument.createRange();
1012 nodeRange.selectNode(node);
1014 nodeRange.selectNodeContents(node);
1016 if (collapse === true) {
1017 nodeRange.collapse(true);
1020 var s = this.win.getSelection();
1021 s.removeAllRanges();
1022 s.addRange(nodeRange);
1025 getSelectedNode: function()
1027 // this may only work on Gecko!!!
1029 // should we cache this!!!!
1034 var range = this.createRange(this.getSelection()).cloneRange();
1037 var parent = range.parentElement();
1039 var testRange = range.duplicate();
1040 testRange.moveToElementText(parent);
1041 if (testRange.inRange(range)) {
1044 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
1047 parent = parent.parentElement;
1052 // is ancestor a text element.
1053 var ac = range.commonAncestorContainer;
1054 if (ac.nodeType == 3) {
1058 var ar = ac.childNodes;
1061 var other_nodes = [];
1062 var has_other_nodes = false;
1063 for (var i=0;i<ar.length;i++) {
1064 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
1067 // fullly contained node.
1069 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
1074 // probably selected..
1075 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
1076 other_nodes.push(ar[i]);
1080 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
1085 has_other_nodes = true;
1087 if (!nodes.length && other_nodes.length) {
1090 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
1096 createRange: function(sel)
1098 // this has strange effects when using with
1099 // top toolbar - not sure if it's a great idea.
1100 //this.editor.contentWindow.focus();
1101 if (typeof sel != "undefined") {
1103 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
1105 return this.doc.createRange();
1108 return this.doc.createRange();
1111 getParentElement: function()
1114 this.assignDocWin();
1115 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
1117 var range = this.createRange(sel);
1120 var p = range.commonAncestorContainer;
1121 while (p.nodeType == 3) { // text node
1132 * Range intersection.. the hard stuff...
1136 * [ -- selected range --- ]
1140 * if end is before start or hits it. fail.
1141 * if start is after end or hits it fail.
1143 * if either hits (but other is outside. - then it's not
1149 // @see http://www.thismuchiknow.co.uk/?p=64.
1150 rangeIntersectsNode : function(range, node)
1152 var nodeRange = node.ownerDocument.createRange();
1154 nodeRange.selectNode(node);
1156 nodeRange.selectNodeContents(node);
1159 var rangeStartRange = range.cloneRange();
1160 rangeStartRange.collapse(true);
1162 var rangeEndRange = range.cloneRange();
1163 rangeEndRange.collapse(false);
1165 var nodeStartRange = nodeRange.cloneRange();
1166 nodeStartRange.collapse(true);
1168 var nodeEndRange = nodeRange.cloneRange();
1169 nodeEndRange.collapse(false);
1171 return rangeStartRange.compareBoundaryPoints(
1172 Range.START_TO_START, nodeEndRange) == -1 &&
1173 rangeEndRange.compareBoundaryPoints(
1174 Range.START_TO_START, nodeStartRange) == 1;
1178 rangeCompareNode : function(range, node)
1180 var nodeRange = node.ownerDocument.createRange();
1182 nodeRange.selectNode(node);
1184 nodeRange.selectNodeContents(node);
1188 range.collapse(true);
1190 nodeRange.collapse(true);
1192 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1193 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
1195 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1197 var nodeIsBefore = ss == 1;
1198 var nodeIsAfter = ee == -1;
1200 if (nodeIsBefore && nodeIsAfter) {
1203 if (!nodeIsBefore && nodeIsAfter) {
1204 return 1; //right trailed.
1207 if (nodeIsBefore && !nodeIsAfter) {
1208 return 2; // left trailed.
1214 cleanWordChars : function(input) {// change the chars to hex code
1217 [ 8211, "–" ],
1218 [ 8212, "—" ],
1227 Roo.each(swapCodes, function(sw) {
1228 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1230 output = output.replace(swapper, sw[1]);
1240 cleanUpChild : function (node)
1243 new Roo.htmleditor.FilterComment({node : node});
1244 new Roo.htmleditor.FilterAttributes({
1246 attrib_black : this.ablack,
1247 attrib_clean : this.aclean,
1248 style_white : this.cwhite,
1249 style_black : this.cblack
1251 new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
1252 new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
1258 * Clean up MS wordisms...
1259 * @deprecated - use filter directly
1261 cleanWord : function(node)
1263 new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
1270 * @deprecated - use filters
1272 cleanTableWidths : function(node)
1274 new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
1281 applyBlacklists : function()
1283 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1284 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1286 this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean : Roo.HtmlEditorCore.aclean;
1287 this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack : Roo.HtmlEditorCore.ablack;
1288 this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove : Roo.HtmlEditorCore.tag_remove;
1292 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1293 if (b.indexOf(tag) > -1) {
1296 this.white.push(tag);
1300 Roo.each(w, function(tag) {
1301 if (b.indexOf(tag) > -1) {
1304 if (this.white.indexOf(tag) > -1) {
1307 this.white.push(tag);
1312 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1313 if (w.indexOf(tag) > -1) {
1316 this.black.push(tag);
1320 Roo.each(b, function(tag) {
1321 if (w.indexOf(tag) > -1) {
1324 if (this.black.indexOf(tag) > -1) {
1327 this.black.push(tag);
1332 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1333 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1337 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1338 if (b.indexOf(tag) > -1) {
1341 this.cwhite.push(tag);
1345 Roo.each(w, function(tag) {
1346 if (b.indexOf(tag) > -1) {
1349 if (this.cwhite.indexOf(tag) > -1) {
1352 this.cwhite.push(tag);
1357 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1358 if (w.indexOf(tag) > -1) {
1361 this.cblack.push(tag);
1365 Roo.each(b, function(tag) {
1366 if (w.indexOf(tag) > -1) {
1369 if (this.cblack.indexOf(tag) > -1) {
1372 this.cblack.push(tag);
1377 setStylesheets : function(stylesheets)
1379 if(typeof(stylesheets) == 'string'){
1380 Roo.get(this.iframe.contentDocument.head).createChild({
1391 Roo.each(stylesheets, function(s) {
1396 Roo.get(_this.iframe.contentDocument.head).createChild({
1407 removeStylesheets : function()
1411 Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1416 setStyle : function(style)
1418 Roo.get(this.iframe.contentDocument.head).createChild({
1427 // hide stuff that is not compatible
1445 * @cfg {String} fieldClass @hide
1448 * @cfg {String} focusClass @hide
1451 * @cfg {String} autoCreate @hide
1454 * @cfg {String} inputType @hide
1457 * @cfg {String} invalidClass @hide
1460 * @cfg {String} invalidText @hide
1463 * @cfg {String} msgFx @hide
1466 * @cfg {String} validateOnBlur @hide
1470 Roo.HtmlEditorCore.white = [
1471 'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
1473 'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD', 'DIR', 'DIV',
1474 'DL', 'DT', 'H1', 'H2', 'H3', 'H4',
1475 'H5', 'H6', 'HR', 'ISINDEX', 'LISTING', 'MARQUEE',
1476 'MENU', 'MULTICOL', 'OL', 'P', 'PLAINTEXT', 'PRE',
1477 'TABLE', 'UL', 'XMP',
1479 'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH',
1482 'DIR', 'MENU', 'OL', 'UL', 'DL',
1488 Roo.HtmlEditorCore.black = [
1489 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1491 'BASE', 'BASEFONT', 'BGSOUND', 'BLINK', 'BODY',
1492 'FRAME', 'FRAMESET', 'HEAD', 'HTML', 'ILAYER',
1493 'IFRAME', 'LAYER', 'LINK', 'META', 'OBJECT',
1494 'SCRIPT', 'STYLE' ,'TITLE', 'XML',
1495 //'FONT' // CLEAN LATER..
1496 'COLGROUP', 'COL' // messy tables.
1499 Roo.HtmlEditorCore.clean = [ // ?? needed???
1500 'SCRIPT', 'STYLE', 'TITLE', 'XML'
1502 Roo.HtmlEditorCore.tag_remove = [
1507 Roo.HtmlEditorCore.ablack = [
1511 Roo.HtmlEditorCore.aclean = [
1512 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1516 Roo.HtmlEditorCore.pwhite= [
1517 'http', 'https', 'mailto'
1520 // white listed style attributes.
1521 Roo.HtmlEditorCore.cwhite= [
1522 // 'text-align', /// default is to allow most things..
1528 // black listed style attributes.
1529 Roo.HtmlEditorCore.cblack= [
1530 // 'font-size' -- this can be set by the project