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
79 // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
81 // defaults : white / black...
82 this.applyBlacklists();
89 Roo.extend(Roo.HtmlEditorCore, Roo.Component, {
93 * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field
99 * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
104 * @cfg {Number} height (in pixels)
108 * @cfg {Number} width (in pixels)
112 * @cfg {boolean} autoClean - default true - loading and saving will remove quite a bit of formating,
113 * if you are doing an email editor, this probably needs disabling, it's designed
118 * @cfg {boolean} enableBlocks - default true - if the block editor (table and figure should be enabled)
122 * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
128 * @cfg {boolean} allowComments - default false - allow comments in HTML source
129 * - by default they are stripped - if you are editing email you may need this.
131 allowComments: false,
135 // private properties
136 validationEvent : false,
140 sourceEditMode : false,
141 onFocus : Roo.emptyFn,
147 // blacklist + whitelisted elements..
156 * Protected method that will not generally be called directly. It
157 * is called when the editor initializes the iframe with HTML contents. Override this method if you
158 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
160 getDocMarkup : function(){
164 // inherit styels from page...??
165 if (this.stylesheets === false) {
167 Roo.get(document.head).select('style').each(function(node) {
168 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
171 Roo.get(document.head).select('link').each(function(node) {
172 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
175 } else if (!this.stylesheets.length) {
177 st = '<style type="text/css">' +
178 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
181 for (var i in this.stylesheets) {
182 if (typeof(this.stylesheets[i]) != 'string') {
185 st += '<link rel="stylesheet" href="' + this.stylesheets[i] +'" type="text/css">';
190 st += '<style type="text/css">' +
191 'IMG { cursor: pointer } ' +
194 var cls = 'roo-htmleditor-body';
196 if(this.bodyCls.length){
197 cls += ' ' + this.bodyCls;
200 return '<html><head>' + st +
201 //<style type="text/css">' +
202 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
204 ' </head><body contenteditable="true" data-enable-grammerly="true" class="' + cls + '"></body></html>';
208 onRender : function(ct, position)
211 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
212 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
215 this.el.dom.style.border = '0 none';
216 this.el.dom.setAttribute('tabIndex', -1);
217 this.el.addClass('x-hidden hide');
221 if(Roo.isIE){ // fix IE 1px bogus margin
222 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
226 this.frameId = Roo.id();
230 var iframe = this.owner.wrap.createChild({
232 cls: 'form-control', // bootstrap..
236 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
241 this.iframe = iframe.dom;
245 this.doc.designMode = 'on';
248 this.doc.write(this.getDocMarkup());
252 var task = { // must defer to wait for browser to be ready
254 //console.log("run task?" + this.doc.readyState);
256 if(this.doc.body || this.doc.readyState == 'complete'){
258 this.doc.designMode="on";
263 Roo.TaskMgr.stop(task);
264 this.initEditor.defer(10, this);
271 Roo.TaskMgr.start(task);
276 onResize : function(w, h)
278 Roo.log('resize: ' +w + ',' + h );
279 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
283 if(typeof w == 'number'){
285 this.iframe.style.width = w + 'px';
287 if(typeof h == 'number'){
289 this.iframe.style.height = h + 'px';
291 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
298 * Toggles the editor between standard and source edit mode.
299 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
301 toggleSourceEdit : function(sourceEditMode){
303 this.sourceEditMode = sourceEditMode === true;
305 if(this.sourceEditMode){
307 Roo.get(this.iframe).addClass(['x-hidden','hide', 'd-none']); //FIXME - what's the BS styles for these
310 Roo.get(this.iframe).removeClass(['x-hidden','hide', 'd-none']);
311 //this.iframe.className = '';
314 //this.setSize(this.owner.wrap.getSize());
315 //this.fireEvent('editmodechange', this, this.sourceEditMode);
322 * Protected method that will not generally be called directly. If you need/want
323 * custom HTML cleanup, this is the method you should override.
324 * @param {String} html The HTML to be cleaned
325 * return {String} The cleaned HTML
327 cleanHtml : function(html){
330 if(Roo.isSafari){ // strip safari nonsense
331 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
334 if(html == ' '){
341 * HTML Editor -> Textarea
342 * Protected method that will not generally be called directly. Syncs the contents
343 * of the editor iframe with the textarea.
345 syncValue : function()
347 //Roo.log("HtmlEditorCore:syncValue (EDITOR->TEXT)");
348 if(this.initialized){
350 this.undoManager.addEvent();
353 var bd = (this.doc.body || this.doc.documentElement);
357 var div = document.createElement('div');
358 div.innerHTML = bd.innerHTML;
361 if (this.enableBlocks) {
362 new Roo.htmleditor.FilterBlock({ node : div });
367 var html = div.innerHTML;
369 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
370 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
372 html = '<div style="'+m[0]+'">' + html + '</div>';
375 html = this.cleanHtml(html);
376 // fix up the special chars.. normaly like back quotes in word...
377 // however we do not want to do this with chinese..
378 html = html.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u0080-\uFFFF]/g, function(match) {
380 var cc = match.charCodeAt();
382 // Get the character value, handling surrogate pairs
383 if (match.length == 2) {
384 // It's a surrogate pair, calculate the Unicode code point
385 var high = match.charCodeAt(0) - 0xD800;
386 var low = match.charCodeAt(1) - 0xDC00;
387 cc = (high * 0x400) + low + 0x10000;
389 (cc >= 0x4E00 && cc < 0xA000 ) ||
390 (cc >= 0x3400 && cc < 0x4E00 ) ||
391 (cc >= 0xf900 && cc < 0xfb00 )
396 // No, use a numeric entity. Here we brazenly (and possibly mistakenly)
397 return "&#" + cc + ";";
404 if(this.owner.fireEvent('beforesync', this, html) !== false){
405 this.el.dom.value = html;
406 this.owner.fireEvent('sync', this, html);
412 * TEXTAREA -> EDITABLE
413 * Protected method that will not generally be called directly. Pushes the value of the textarea
414 * into the iframe editor.
416 pushValue : function()
418 //Roo.log("HtmlEditorCore:pushValue (TEXT->EDITOR)");
419 if(this.initialized){
420 var v = this.el.dom.value.trim();
423 if(this.owner.fireEvent('beforepush', this, v) !== false){
424 var d = (this.doc.body || this.doc.documentElement);
427 this.el.dom.value = d.innerHTML;
428 this.owner.fireEvent('push', this, v);
430 if (this.autoClean) {
431 new Roo.htmleditor.FilterParagraph({node : this.doc.body}); // paragraphs
432 new Roo.htmleditor.FilterSpan({node : this.doc.body}); // empty spans
435 Roo.htmleditor.Block.initAll(this.doc.body);
437 var lc = this.doc.body.lastChild;
438 if (lc && lc.nodeType == 1 && lc.getAttribute("contenteditable") == "false") {
439 // add an extra line at the end.
440 this.doc.body.appendChild(this.doc.createElement('br'));
448 deferFocus : function(){
449 this.focus.defer(10, this);
454 if(this.win && !this.sourceEditMode){
461 assignDocWin: function()
463 var iframe = this.iframe;
466 this.doc = iframe.contentWindow.document;
467 this.win = iframe.contentWindow;
469 // if (!Roo.get(this.frameId)) {
472 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
473 // this.win = Roo.get(this.frameId).dom.contentWindow;
475 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
479 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
480 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
485 initEditor : function(){
486 //console.log("INIT EDITOR");
491 this.doc.designMode="on";
493 this.doc.write(this.getDocMarkup());
496 var dbody = (this.doc.body || this.doc.documentElement);
497 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
498 // this copies styles from the containing element into thsi one..
499 // not sure why we need all of this..
500 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
502 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
503 //ss['background-attachment'] = 'fixed'; // w3c
504 dbody.bgProperties = 'fixed'; // ie
505 //Roo.DomHelper.applyStyles(dbody, ss);
506 Roo.EventManager.on(this.doc, {
507 //'mousedown': this.onEditorEvent,
508 'mouseup': this.onEditorEvent,
509 'dblclick': this.onEditorEvent,
510 'click': this.onEditorEvent,
511 'keyup': this.onEditorEvent,
516 Roo.EventManager.on(this.doc, {
517 'paste': this.onPasteEvent,
521 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
524 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
525 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
527 this.initialized = true;
530 // initialize special key events - enter
531 new Roo.htmleditor.KeyEnter({core : this});
535 this.owner.fireEvent('initialize', this);
539 onPasteEvent : function(e,v)
541 // I think we better assume paste is going to be a dirty load of rubish from word..
543 // even pasting into a 'email version' of this widget will have to clean up that mess.
544 var cd = (e.browserEvent.clipboardData || window.clipboardData);
546 // check what type of paste - if it's an image, then handle it differently.
547 if (cd.files.length > 0) {
549 var urlAPI = (window.createObjectURL && window) ||
550 (window.URL && URL.revokeObjectURL && URL) ||
551 (window.webkitURL && webkitURL);
553 var url = urlAPI.createObjectURL( cd.files[0]);
554 this.insertAtCursor('<img src=" + url + ">');
558 var html = cd.getData('text/html'); // clipboard event
559 var parser = new Roo.rtf.Parser(cd.getData('text/rtf'));
560 var images = parser.doc ? parser.doc.getElementsByType('pict') : [];
564 images = images.filter(function(g) { return !g.path.match(/^rtf\/(head|pgdsctbl|listtable)/); }) // ignore headers
565 .map(function(g) { return g.toDataURL(); });
568 html = this.cleanWordChars(html);
570 var d = (new DOMParser().parseFromString(html, 'text/html')).body;
573 var sn = this.getParentElement();
574 // check if d contains a table, and prevent nesting??
575 //Roo.log(d.getElementsByTagName('table'));
577 //Roo.log(sn.closest('table'));
578 if (d.getElementsByTagName('table').length && sn && sn.closest('table')) {
580 this.insertAtCursor("You can not nest tables");
581 //Roo.log("prevent?"); // fixme -
585 if (images.length > 0) {
586 Roo.each(d.getElementsByTagName('img'), function(img, i) {
587 img.setAttribute('src', images[i]);
590 if (this.autoClean) {
591 new Roo.htmleditor.FilterStyleToTag({ node : d });
592 new Roo.htmleditor.FilterAttributes({
594 attrib_white : ['href', 'src', 'name', 'align'],
595 attrib_clean : ['href', 'src' ]
597 new Roo.htmleditor.FilterBlack({ node : d, tag : this.black});
599 new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT' ]} );
600 new Roo.htmleditor.FilterParagraph({ node : d });
601 new Roo.htmleditor.FilterSpan({ node : d });
602 new Roo.htmleditor.FilterLongBr({ node : d });
604 if (this.enableBlocks) {
606 Array.from(d.getElementsByTagName('img')).forEach(function(img) {
607 if (img.closest('figure')) { // assume!! that it's aready
610 var fig = new Roo.htmleditor.BlockFigure({
613 fig.updateElement(img); // replace it..
619 this.insertAtCursor(d.innerHTML.replace(/ /g,' '));
620 if (this.enableBlocks) {
621 Roo.htmleditor.Block.initAll(this.doc.body);
627 // default behaveiour should be our local cleanup paste? (optional?)
628 // for simple editor - we want to hammer the paste and get rid of everything... - so over-rideable..
629 //this.owner.fireEvent('paste', e, v);
632 onDestroy : function(){
638 //for (var i =0; i < this.toolbars.length;i++) {
639 // // fixme - ask toolbars for heights?
640 // this.toolbars[i].onDestroy();
643 //this.wrap.dom.innerHTML = '';
644 //this.wrap.remove();
649 onFirstFocus : function(){
652 this.undoManager = new Roo.lib.UndoManager(100,(this.doc.body || this.doc.documentElement));
654 this.activated = true;
657 if(Roo.isGecko){ // prevent silly gecko errors
659 var s = this.win.getSelection();
660 if(!s.focusNode || s.focusNode.nodeType != 3){
661 var r = s.getRangeAt(0);
662 r.selectNodeContents((this.doc.body || this.doc.documentElement));
667 this.execCmd('useCSS', true);
668 this.execCmd('styleWithCSS', false);
671 this.owner.fireEvent('activate', this);
675 adjustFont: function(btn){
676 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
677 //if(Roo.isSafari){ // safari
680 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
681 if(Roo.isSafari){ // safari
682 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
683 v = (v < 10) ? 10 : v;
684 v = (v > 48) ? 48 : v;
685 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
690 v = Math.max(1, v+adjust);
692 this.execCmd('FontSize', v );
695 onEditorEvent : function(e)
698 if (e && (e.ctrlKey || e.metaKey) && e.keyCode === 90) {
699 return; // we do not handle this.. (undo manager does..)
701 // in theory this detects if the last element is not a br, then we try and do that.
702 // its so clicking in space at bottom triggers adding a br and moving the cursor.
704 e.target.nodeName == 'BODY' &&
705 e.type == "mouseup" &&
706 this.doc.body.lastChild
708 var lc = this.doc.body.lastChild;
709 // gtx-trans is google translate plugin adding crap.
710 while ((lc.nodeType == 3 && lc.nodeValue == '') || lc.id == 'gtx-trans') {
711 lc = lc.previousSibling;
713 if (lc.nodeType == 1 && lc.nodeName != 'BR') {
714 // if last element is <BR> - then dont do anything.
716 var ns = this.doc.createElement('br');
717 this.doc.body.appendChild(ns);
718 range = this.doc.createRange();
719 range.setStartAfter(ns);
720 range.collapse(true);
721 var sel = this.win.getSelection();
722 sel.removeAllRanges();
729 this.fireEditorEvent(e);
730 // this.updateToolbar();
731 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
734 fireEditorEvent: function(e)
736 this.owner.fireEvent('editorevent', this, e);
739 insertTag : function(tg)
741 // could be a bit smarter... -> wrap the current selected tRoo..
742 if (tg.toLowerCase() == 'span' ||
743 tg.toLowerCase() == 'code' ||
744 tg.toLowerCase() == 'sup' ||
745 tg.toLowerCase() == 'sub'
748 range = this.createRange(this.getSelection());
749 var wrappingNode = this.doc.createElement(tg.toLowerCase());
750 wrappingNode.appendChild(range.extractContents());
751 range.insertNode(wrappingNode);
758 this.execCmd("formatblock", tg);
759 this.undoManager.addEvent();
762 insertText : function(txt)
766 var range = this.createRange();
767 range.deleteContents();
768 //alert(Sender.getAttribute('label'));
770 range.insertNode(this.doc.createTextNode(txt));
771 this.undoManager.addEvent();
777 * Executes a Midas editor command on the editor document and performs necessary focus and
778 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
779 * @param {String} cmd The Midas command
780 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
782 relayCmd : function(cmd, value)
788 case 'justifycenter':
789 // if we are in a cell, then we will adjust the
790 var n = this.getParentElement();
791 var td = n.closest('td');
793 var bl = Roo.htmleditor.Block.factory(td);
794 bl.textAlign = cmd.replace('justify','');
796 this.owner.fireEvent('editorevent', this);
799 this.execCmd('styleWithCSS', true); //
803 // if there is no selection, then we insert, and set the curson inside it..
804 this.execCmd('styleWithCSS', false);
814 this.execCmd(cmd, value);
815 this.owner.fireEvent('editorevent', this);
816 //this.updateToolbar();
817 this.owner.deferFocus();
821 * Executes a Midas editor command directly on the editor document.
822 * For visual commands, you should use {@link #relayCmd} instead.
823 * <b>This should only be called after the editor is initialized.</b>
824 * @param {String} cmd The Midas command
825 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
827 execCmd : function(cmd, value){
828 this.doc.execCommand(cmd, false, value === undefined ? null : value);
835 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
837 * @param {String} text | dom node..
839 insertAtCursor : function(text)
846 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
850 // from jquery ui (MIT licenced)
854 if (win.getSelection && win.getSelection().getRangeAt) {
856 // delete the existing?
858 this.createRange(this.getSelection()).deleteContents();
859 range = win.getSelection().getRangeAt(0);
860 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
861 range.insertNode(node);
862 range = range.cloneRange();
863 range.collapse(false);
865 win.getSelection().removeAllRanges();
866 win.getSelection().addRange(range);
870 } else if (win.document.selection && win.document.selection.createRange) {
871 // no firefox support
872 var txt = typeof(text) == 'string' ? text : text.outerHTML;
873 win.document.selection.createRange().pasteHTML(txt);
876 // no firefox support
877 var txt = typeof(text) == 'string' ? text : text.outerHTML;
878 this.execCmd('InsertHTML', txt);
886 mozKeyPress : function(e){
888 var c = e.getCharCode(), cmd;
891 c = String.fromCharCode(c).toLowerCase();
905 // this.cleanUpPaste.defer(100, this);
923 fixKeys : function(){ // load time branching for fastest keydown performance
928 var k = e.getKey(), r;
931 r = this.doc.selection.createRange();
934 r.pasteHTML('    ');
939 /// this is handled by Roo.htmleditor.KeyEnter
942 r = this.doc.selection.createRange();
944 var target = r.parentElement();
945 if(!target || target.tagName.toLowerCase() != 'li'){
947 r.pasteHTML('<br/>');
954 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
955 // this.cleanUpPaste.defer(100, this);
961 }else if(Roo.isOpera){
967 this.execCmd('InsertHTML','    ');
971 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
972 // this.cleanUpPaste.defer(100, this);
977 }else if(Roo.isSafari){
983 this.execCmd('InsertText','\t');
989 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
990 // this.cleanUpPaste.defer(100, this);
998 getAllAncestors: function()
1000 var p = this.getSelectedNode();
1003 a.push(p); // push blank onto stack..
1004 p = this.getParentElement();
1008 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
1012 a.push(this.doc.body);
1016 lastSelNode : false,
1019 getSelection : function()
1021 this.assignDocWin();
1022 return Roo.lib.Selection.wrap(Roo.isIE ? this.doc.selection : this.win.getSelection(), this.doc);
1026 * @param {DomElement} node the node to select
1028 selectNode : function(node, collapse)
1030 var nodeRange = node.ownerDocument.createRange();
1032 nodeRange.selectNode(node);
1034 nodeRange.selectNodeContents(node);
1036 if (collapse === true) {
1037 nodeRange.collapse(true);
1040 var s = this.win.getSelection();
1041 s.removeAllRanges();
1042 s.addRange(nodeRange);
1045 getSelectedNode: function()
1047 // this may only work on Gecko!!!
1049 // should we cache this!!!!
1054 var range = this.createRange(this.getSelection()).cloneRange();
1057 var parent = range.parentElement();
1059 var testRange = range.duplicate();
1060 testRange.moveToElementText(parent);
1061 if (testRange.inRange(range)) {
1064 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
1067 parent = parent.parentElement;
1072 // is ancestor a text element.
1073 var ac = range.commonAncestorContainer;
1074 if (ac.nodeType == 3) {
1078 var ar = ac.childNodes;
1081 var other_nodes = [];
1082 var has_other_nodes = false;
1083 for (var i=0;i<ar.length;i++) {
1084 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
1087 // fullly contained node.
1089 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
1094 // probably selected..
1095 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
1096 other_nodes.push(ar[i]);
1100 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
1105 has_other_nodes = true;
1107 if (!nodes.length && other_nodes.length) {
1110 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
1116 createRange: function(sel)
1118 // this has strange effects when using with
1119 // top toolbar - not sure if it's a great idea.
1120 //this.editor.contentWindow.focus();
1121 if (typeof sel != "undefined") {
1123 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
1125 return this.doc.createRange();
1128 return this.doc.createRange();
1131 getParentElement: function()
1134 this.assignDocWin();
1135 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
1137 var range = this.createRange(sel);
1140 var p = range.commonAncestorContainer;
1141 while (p.nodeType == 3) { // text node
1152 * Range intersection.. the hard stuff...
1156 * [ -- selected range --- ]
1160 * if end is before start or hits it. fail.
1161 * if start is after end or hits it fail.
1163 * if either hits (but other is outside. - then it's not
1169 // @see http://www.thismuchiknow.co.uk/?p=64.
1170 rangeIntersectsNode : function(range, node)
1172 var nodeRange = node.ownerDocument.createRange();
1174 nodeRange.selectNode(node);
1176 nodeRange.selectNodeContents(node);
1179 var rangeStartRange = range.cloneRange();
1180 rangeStartRange.collapse(true);
1182 var rangeEndRange = range.cloneRange();
1183 rangeEndRange.collapse(false);
1185 var nodeStartRange = nodeRange.cloneRange();
1186 nodeStartRange.collapse(true);
1188 var nodeEndRange = nodeRange.cloneRange();
1189 nodeEndRange.collapse(false);
1191 return rangeStartRange.compareBoundaryPoints(
1192 Range.START_TO_START, nodeEndRange) == -1 &&
1193 rangeEndRange.compareBoundaryPoints(
1194 Range.START_TO_START, nodeStartRange) == 1;
1198 rangeCompareNode : function(range, node)
1200 var nodeRange = node.ownerDocument.createRange();
1202 nodeRange.selectNode(node);
1204 nodeRange.selectNodeContents(node);
1208 range.collapse(true);
1210 nodeRange.collapse(true);
1212 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1213 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
1215 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1217 var nodeIsBefore = ss == 1;
1218 var nodeIsAfter = ee == -1;
1220 if (nodeIsBefore && nodeIsAfter) {
1223 if (!nodeIsBefore && nodeIsAfter) {
1224 return 1; //right trailed.
1227 if (nodeIsBefore && !nodeIsAfter) {
1228 return 2; // left trailed.
1234 cleanWordChars : function(input) {// change the chars to hex code
1237 [ 8211, "–" ],
1238 [ 8212, "—" ],
1247 Roo.each(swapCodes, function(sw) {
1248 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1250 output = output.replace(swapper, sw[1]);
1260 cleanUpChild : function (node)
1263 new Roo.htmleditor.FilterComment({node : node});
1264 new Roo.htmleditor.FilterAttributes({
1266 attrib_black : this.ablack,
1267 attrib_clean : this.aclean,
1268 style_white : this.cwhite,
1269 style_black : this.cblack
1271 new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
1272 new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
1278 * Clean up MS wordisms...
1279 * @deprecated - use filter directly
1281 cleanWord : function(node)
1283 new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
1290 * @deprecated - use filters
1292 cleanTableWidths : function(node)
1294 new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
1301 applyBlacklists : function()
1303 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1304 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1306 this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean : Roo.HtmlEditorCore.aclean;
1307 this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack : Roo.HtmlEditorCore.ablack;
1308 this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove : Roo.HtmlEditorCore.tag_remove;
1312 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1313 if (b.indexOf(tag) > -1) {
1316 this.white.push(tag);
1320 Roo.each(w, function(tag) {
1321 if (b.indexOf(tag) > -1) {
1324 if (this.white.indexOf(tag) > -1) {
1327 this.white.push(tag);
1332 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1333 if (w.indexOf(tag) > -1) {
1336 this.black.push(tag);
1340 Roo.each(b, function(tag) {
1341 if (w.indexOf(tag) > -1) {
1344 if (this.black.indexOf(tag) > -1) {
1347 this.black.push(tag);
1352 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1353 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1357 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1358 if (b.indexOf(tag) > -1) {
1361 this.cwhite.push(tag);
1365 Roo.each(w, function(tag) {
1366 if (b.indexOf(tag) > -1) {
1369 if (this.cwhite.indexOf(tag) > -1) {
1372 this.cwhite.push(tag);
1377 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1378 if (w.indexOf(tag) > -1) {
1381 this.cblack.push(tag);
1385 Roo.each(b, function(tag) {
1386 if (w.indexOf(tag) > -1) {
1389 if (this.cblack.indexOf(tag) > -1) {
1392 this.cblack.push(tag);
1397 setStylesheets : function(stylesheets)
1399 if(typeof(stylesheets) == 'string'){
1400 Roo.get(this.iframe.contentDocument.head).createChild({
1411 Roo.each(stylesheets, function(s) {
1416 Roo.get(_this.iframe.contentDocument.head).createChild({
1427 removeStylesheets : function()
1431 Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1436 setStyle : function(style)
1438 Roo.get(this.iframe.contentDocument.head).createChild({
1447 // hide stuff that is not compatible
1465 * @cfg {String} fieldClass @hide
1468 * @cfg {String} focusClass @hide
1471 * @cfg {String} autoCreate @hide
1474 * @cfg {String} inputType @hide
1477 * @cfg {String} invalidClass @hide
1480 * @cfg {String} invalidText @hide
1483 * @cfg {String} msgFx @hide
1486 * @cfg {String} validateOnBlur @hide
1490 Roo.HtmlEditorCore.white = [
1491 'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
1493 'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD', 'DIR', 'DIV',
1494 'DL', 'DT', 'H1', 'H2', 'H3', 'H4',
1495 'H5', 'H6', 'HR', 'ISINDEX', 'LISTING', 'MARQUEE',
1496 'MENU', 'MULTICOL', 'OL', 'P', 'PLAINTEXT', 'PRE',
1497 'TABLE', 'UL', 'XMP',
1499 'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH',
1502 'DIR', 'MENU', 'OL', 'UL', 'DL',
1508 Roo.HtmlEditorCore.black = [
1509 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1511 'BASE', 'BASEFONT', 'BGSOUND', 'BLINK', 'BODY',
1512 'FRAME', 'FRAMESET', 'HEAD', 'HTML', 'ILAYER',
1513 'IFRAME', 'LAYER', 'LINK', 'META', 'OBJECT',
1514 'SCRIPT', 'STYLE' ,'TITLE', 'XML',
1515 //'FONT' // CLEAN LATER..
1516 'COLGROUP', 'COL' // messy tables.
1519 Roo.HtmlEditorCore.clean = [ // ?? needed???
1520 'SCRIPT', 'STYLE', 'TITLE', 'XML'
1522 Roo.HtmlEditorCore.tag_remove = [
1527 Roo.HtmlEditorCore.ablack = [
1531 Roo.HtmlEditorCore.aclean = [
1532 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1536 Roo.HtmlEditorCore.pwhite= [
1537 'http', 'https', 'mailto'
1540 // white listed style attributes.
1541 Roo.HtmlEditorCore.cwhite= [
1542 // 'text-align', /// default is to allow most things..
1548 // black listed style attributes.
1549 Roo.HtmlEditorCore.cblack= [
1550 // 'font-size' -- this can be set by the project