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);
425 Roo.htmleditor.Block.initAll(this.doc.body);
427 var lc = this.doc.body.lastChild;
428 if (lc && lc.nodeType == 1 && lc.getAttribute("contenteditable") == "false") {
429 // add an extra line at the end.
430 this.doc.body.appendChild(this.doc.createElement('br'));
438 deferFocus : function(){
439 this.focus.defer(10, this);
444 if(this.win && !this.sourceEditMode){
451 assignDocWin: function()
453 var iframe = this.iframe;
456 this.doc = iframe.contentWindow.document;
457 this.win = iframe.contentWindow;
459 // if (!Roo.get(this.frameId)) {
462 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
463 // this.win = Roo.get(this.frameId).dom.contentWindow;
465 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
469 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
470 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
475 initEditor : function(){
476 //console.log("INIT EDITOR");
481 this.doc.designMode="on";
483 this.doc.write(this.getDocMarkup());
486 var dbody = (this.doc.body || this.doc.documentElement);
487 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
488 // this copies styles from the containing element into thsi one..
489 // not sure why we need all of this..
490 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
492 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
493 //ss['background-attachment'] = 'fixed'; // w3c
494 dbody.bgProperties = 'fixed'; // ie
495 //Roo.DomHelper.applyStyles(dbody, ss);
496 Roo.EventManager.on(this.doc, {
497 //'mousedown': this.onEditorEvent,
498 'mouseup': this.onEditorEvent,
499 'dblclick': this.onEditorEvent,
500 'click': this.onEditorEvent,
501 'keyup': this.onEditorEvent,
506 Roo.EventManager.on(this.doc, {
507 'paste': this.onPasteEvent,
511 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
513 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
514 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
516 this.initialized = true;
519 // initialize special key events - enter
520 new Roo.htmleditor.KeyEnter({core : this});
524 this.owner.fireEvent('initialize', this);
528 onPasteEvent : function(e,v)
530 // I think we better assume paste is going to be a dirty load of rubish from word..
532 // even pasting into a 'email version' of this widget will have to clean up that mess.
533 var cd = (e.browserEvent.clipboardData || window.clipboardData);
535 // check what type of paste - if it's an image, then handle it differently.
536 if (cd.files.length > 0) {
538 var urlAPI = (window.createObjectURL && window) ||
539 (window.URL && URL.revokeObjectURL && URL) ||
540 (window.webkitURL && webkitURL);
542 var url = urlAPI.createObjectURL( cd.files[0]);
543 this.insertAtCursor('<img src=" + url + ">');
547 var html = cd.getData('text/html'); // clipboard event
548 var parser = new Roo.rtf.Parser(cd.getData('text/rtf'));
549 var images = parser.doc ? parser.doc.getElementsByType('pict') : [];
553 images = images.filter(function(g) { return !g.path.match(/^rtf\/(head|pgdsctbl|listtable)/); }) // ignore headers
554 .map(function(g) { return g.toDataURL(); });
557 html = this.cleanWordChars(html);
559 var d = (new DOMParser().parseFromString(html, 'text/html')).body;
562 var sn = this.getParentElement();
563 // check if d contains a table, and prevent nesting??
564 //Roo.log(d.getElementsByTagName('table'));
566 //Roo.log(sn.closest('table'));
567 if (d.getElementsByTagName('table').length && sn && sn.closest('table')) {
569 this.insertAtCursor("You can not nest tables");
570 //Roo.log("prevent?"); // fixme -
574 if (images.length > 0) {
575 Roo.each(d.getElementsByTagName('img'), function(img, i) {
576 img.setAttribute('src', images[i]);
581 new Roo.htmleditor.FilterStyleToTag({ node : d });
582 new Roo.htmleditor.FilterAttributes({
584 attrib_white : ['href', 'src', 'name', 'align'],
585 attrib_clean : ['href', 'src' ]
587 new Roo.htmleditor.FilterBlack({ node : d, tag : this.black});
589 new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT' ]} );
590 new Roo.htmleditor.FilterParagraph({ node : d });
591 new Roo.htmleditor.FilterSpan({ node : d });
592 new Roo.htmleditor.FilterLongBr({ node : d });
596 this.insertAtCursor(d.innerHTML);
602 // default behaveiour should be our local cleanup paste? (optional?)
603 // for simple editor - we want to hammer the paste and get rid of everything... - so over-rideable..
604 //this.owner.fireEvent('paste', e, v);
607 onDestroy : function(){
613 //for (var i =0; i < this.toolbars.length;i++) {
614 // // fixme - ask toolbars for heights?
615 // this.toolbars[i].onDestroy();
618 //this.wrap.dom.innerHTML = '';
619 //this.wrap.remove();
624 onFirstFocus : function(){
627 this.undoManager = new Roo.lib.UndoManager(100,(this.doc.body || this.doc.documentElement));
629 this.activated = true;
632 if(Roo.isGecko){ // prevent silly gecko errors
634 var s = this.win.getSelection();
635 if(!s.focusNode || s.focusNode.nodeType != 3){
636 var r = s.getRangeAt(0);
637 r.selectNodeContents((this.doc.body || this.doc.documentElement));
642 this.execCmd('useCSS', true);
643 this.execCmd('styleWithCSS', false);
646 this.owner.fireEvent('activate', this);
650 adjustFont: function(btn){
651 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
652 //if(Roo.isSafari){ // safari
655 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
656 if(Roo.isSafari){ // safari
657 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
658 v = (v < 10) ? 10 : v;
659 v = (v > 48) ? 48 : v;
660 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
665 v = Math.max(1, v+adjust);
667 this.execCmd('FontSize', v );
670 onEditorEvent : function(e)
673 if (e && (e.ctrlKey || e.metaKey) && e.keyCode === 90) {
674 return; // we do not handle this.. (undo manager does..)
676 // in theory this detects if the last element is not a br, then we try and do that.
677 // its so clicking in space at bottom triggers adding a br and moving the cursor.
679 e.target.nodeName == 'BODY' &&
680 e.type == "mouseup" &&
681 this.doc.body.lastChild
683 var lc = this.doc.body.lastChild;
684 while (lc.nodeType == 3 && lc.nodeValue == '') {
685 lc = lc.previousSibling;
687 if (lc.nodeType == 1 && lc.nodeName != 'BR') {
688 // if last element is <BR> - then dont do anything.
690 var ns = this.doc.createElement('br');
691 this.doc.body.appendChild(ns);
692 range = this.doc.createRange();
693 range.setStartAfter(ns);
694 range.collapse(true);
695 var sel = this.win.getSelection();
696 sel.removeAllRanges();
703 this.owner.fireEvent('editorevent', this, e);
704 // this.updateToolbar();
705 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
708 insertTag : function(tg)
710 // could be a bit smarter... -> wrap the current selected tRoo..
711 if (tg.toLowerCase() == 'span' ||
712 tg.toLowerCase() == 'code' ||
713 tg.toLowerCase() == 'sup' ||
714 tg.toLowerCase() == 'sub'
717 range = this.createRange(this.getSelection());
718 var wrappingNode = this.doc.createElement(tg.toLowerCase());
719 wrappingNode.appendChild(range.extractContents());
720 range.insertNode(wrappingNode);
727 this.execCmd("formatblock", tg);
728 this.undoManager.addEvent();
731 insertText : function(txt)
735 var range = this.createRange();
736 range.deleteContents();
737 //alert(Sender.getAttribute('label'));
739 range.insertNode(this.doc.createTextNode(txt));
740 this.undoManager.addEvent();
746 * Executes a Midas editor command on the editor document and performs necessary focus and
747 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
748 * @param {String} cmd The Midas command
749 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
751 relayCmd : function(cmd, value){
753 this.execCmd(cmd, value);
754 this.owner.fireEvent('editorevent', this);
755 //this.updateToolbar();
756 this.owner.deferFocus();
760 * Executes a Midas editor command directly on the editor document.
761 * For visual commands, you should use {@link #relayCmd} instead.
762 * <b>This should only be called after the editor is initialized.</b>
763 * @param {String} cmd The Midas command
764 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
766 execCmd : function(cmd, value){
767 this.doc.execCommand(cmd, false, value === undefined ? null : value);
774 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
776 * @param {String} text | dom node..
778 insertAtCursor : function(text)
785 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
789 // from jquery ui (MIT licenced)
793 if (win.getSelection && win.getSelection().getRangeAt) {
795 // delete the existing?
797 this.createRange(this.getSelection()).deleteContents();
798 range = win.getSelection().getRangeAt(0);
799 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
800 range.insertNode(node);
801 range = range.cloneRange();
802 range.collapse(false);
804 win.getSelection().removeAllRanges();
805 win.getSelection().addRange(range);
809 } else if (win.document.selection && win.document.selection.createRange) {
810 // no firefox support
811 var txt = typeof(text) == 'string' ? text : text.outerHTML;
812 win.document.selection.createRange().pasteHTML(txt);
815 // no firefox support
816 var txt = typeof(text) == 'string' ? text : text.outerHTML;
817 this.execCmd('InsertHTML', txt);
825 mozKeyPress : function(e){
827 var c = e.getCharCode(), cmd;
830 c = String.fromCharCode(c).toLowerCase();
844 // this.cleanUpPaste.defer(100, this);
860 fixKeys : function(){ // load time branching for fastest keydown performance
863 var k = e.getKey(), r;
866 r = this.doc.selection.createRange();
869 r.pasteHTML('    ');
874 /// this is handled by Roo.htmleditor.KeyEnter
877 r = this.doc.selection.createRange();
879 var target = r.parentElement();
880 if(!target || target.tagName.toLowerCase() != 'li'){
882 r.pasteHTML('<br/>');
889 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
890 // this.cleanUpPaste.defer(100, this);
896 }else if(Roo.isOpera){
902 this.execCmd('InsertHTML','    ');
905 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
906 // this.cleanUpPaste.defer(100, this);
911 }else if(Roo.isSafari){
917 this.execCmd('InsertText','\t');
921 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
922 // this.cleanUpPaste.defer(100, this);
930 getAllAncestors: function()
932 var p = this.getSelectedNode();
935 a.push(p); // push blank onto stack..
936 p = this.getParentElement();
940 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
944 a.push(this.doc.body);
951 getSelection : function()
954 return Roo.isIE ? this.doc.selection : this.win.getSelection();
958 * @param {DomElement} node the node to select
960 selectNode : function(node)
962 var nodeRange = node.ownerDocument.createRange();
964 nodeRange.selectNode(node);
966 nodeRange.selectNodeContents(node);
968 //nodeRange.collapse(true);
969 var s = this.win.getSelection();
971 s.addRange(nodeRange);
974 getSelectedNode: function()
976 // this may only work on Gecko!!!
978 // should we cache this!!!!
983 var range = this.createRange(this.getSelection()).cloneRange();
986 var parent = range.parentElement();
988 var testRange = range.duplicate();
989 testRange.moveToElementText(parent);
990 if (testRange.inRange(range)) {
993 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
996 parent = parent.parentElement;
1001 // is ancestor a text element.
1002 var ac = range.commonAncestorContainer;
1003 if (ac.nodeType == 3) {
1007 var ar = ac.childNodes;
1010 var other_nodes = [];
1011 var has_other_nodes = false;
1012 for (var i=0;i<ar.length;i++) {
1013 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
1016 // fullly contained node.
1018 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
1023 // probably selected..
1024 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
1025 other_nodes.push(ar[i]);
1029 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
1034 has_other_nodes = true;
1036 if (!nodes.length && other_nodes.length) {
1039 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
1045 createRange: function(sel)
1047 // this has strange effects when using with
1048 // top toolbar - not sure if it's a great idea.
1049 //this.editor.contentWindow.focus();
1050 if (typeof sel != "undefined") {
1052 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
1054 return this.doc.createRange();
1057 return this.doc.createRange();
1060 getParentElement: function()
1063 this.assignDocWin();
1064 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
1066 var range = this.createRange(sel);
1069 var p = range.commonAncestorContainer;
1070 while (p.nodeType == 3) { // text node
1081 * Range intersection.. the hard stuff...
1085 * [ -- selected range --- ]
1089 * if end is before start or hits it. fail.
1090 * if start is after end or hits it fail.
1092 * if either hits (but other is outside. - then it's not
1098 // @see http://www.thismuchiknow.co.uk/?p=64.
1099 rangeIntersectsNode : function(range, node)
1101 var nodeRange = node.ownerDocument.createRange();
1103 nodeRange.selectNode(node);
1105 nodeRange.selectNodeContents(node);
1108 var rangeStartRange = range.cloneRange();
1109 rangeStartRange.collapse(true);
1111 var rangeEndRange = range.cloneRange();
1112 rangeEndRange.collapse(false);
1114 var nodeStartRange = nodeRange.cloneRange();
1115 nodeStartRange.collapse(true);
1117 var nodeEndRange = nodeRange.cloneRange();
1118 nodeEndRange.collapse(false);
1120 return rangeStartRange.compareBoundaryPoints(
1121 Range.START_TO_START, nodeEndRange) == -1 &&
1122 rangeEndRange.compareBoundaryPoints(
1123 Range.START_TO_START, nodeStartRange) == 1;
1127 rangeCompareNode : function(range, node)
1129 var nodeRange = node.ownerDocument.createRange();
1131 nodeRange.selectNode(node);
1133 nodeRange.selectNodeContents(node);
1137 range.collapse(true);
1139 nodeRange.collapse(true);
1141 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1142 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
1144 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1146 var nodeIsBefore = ss == 1;
1147 var nodeIsAfter = ee == -1;
1149 if (nodeIsBefore && nodeIsAfter) {
1152 if (!nodeIsBefore && nodeIsAfter) {
1153 return 1; //right trailed.
1156 if (nodeIsBefore && !nodeIsAfter) {
1157 return 2; // left trailed.
1163 cleanWordChars : function(input) {// change the chars to hex code
1166 [ 8211, "–" ],
1167 [ 8212, "—" ],
1176 Roo.each(swapCodes, function(sw) {
1177 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1179 output = output.replace(swapper, sw[1]);
1189 cleanUpChild : function (node)
1192 new Roo.htmleditor.FilterComment({node : node});
1193 new Roo.htmleditor.FilterAttributes({
1195 attrib_black : this.ablack,
1196 attrib_clean : this.aclean,
1197 style_white : this.cwhite,
1198 style_black : this.cblack
1200 new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
1201 new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
1207 * Clean up MS wordisms...
1208 * @deprecated - use filter directly
1210 cleanWord : function(node)
1212 new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
1219 * @deprecated - use filters
1221 cleanTableWidths : function(node)
1223 new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
1230 applyBlacklists : function()
1232 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1233 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1235 this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean : Roo.HtmlEditorCore.aclean;
1236 this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack : Roo.HtmlEditorCore.ablack;
1237 this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove : Roo.HtmlEditorCore.tag_remove;
1241 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1242 if (b.indexOf(tag) > -1) {
1245 this.white.push(tag);
1249 Roo.each(w, function(tag) {
1250 if (b.indexOf(tag) > -1) {
1253 if (this.white.indexOf(tag) > -1) {
1256 this.white.push(tag);
1261 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1262 if (w.indexOf(tag) > -1) {
1265 this.black.push(tag);
1269 Roo.each(b, function(tag) {
1270 if (w.indexOf(tag) > -1) {
1273 if (this.black.indexOf(tag) > -1) {
1276 this.black.push(tag);
1281 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1282 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1286 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1287 if (b.indexOf(tag) > -1) {
1290 this.cwhite.push(tag);
1294 Roo.each(w, function(tag) {
1295 if (b.indexOf(tag) > -1) {
1298 if (this.cwhite.indexOf(tag) > -1) {
1301 this.cwhite.push(tag);
1306 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1307 if (w.indexOf(tag) > -1) {
1310 this.cblack.push(tag);
1314 Roo.each(b, function(tag) {
1315 if (w.indexOf(tag) > -1) {
1318 if (this.cblack.indexOf(tag) > -1) {
1321 this.cblack.push(tag);
1326 setStylesheets : function(stylesheets)
1328 if(typeof(stylesheets) == 'string'){
1329 Roo.get(this.iframe.contentDocument.head).createChild({
1340 Roo.each(stylesheets, function(s) {
1345 Roo.get(_this.iframe.contentDocument.head).createChild({
1356 removeStylesheets : function()
1360 Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1365 setStyle : function(style)
1367 Roo.get(this.iframe.contentDocument.head).createChild({
1376 // hide stuff that is not compatible
1394 * @cfg {String} fieldClass @hide
1397 * @cfg {String} focusClass @hide
1400 * @cfg {String} autoCreate @hide
1403 * @cfg {String} inputType @hide
1406 * @cfg {String} invalidClass @hide
1409 * @cfg {String} invalidText @hide
1412 * @cfg {String} msgFx @hide
1415 * @cfg {String} validateOnBlur @hide
1419 Roo.HtmlEditorCore.white = [
1420 'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
1422 'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD', 'DIR', 'DIV',
1423 'DL', 'DT', 'H1', 'H2', 'H3', 'H4',
1424 'H5', 'H6', 'HR', 'ISINDEX', 'LISTING', 'MARQUEE',
1425 'MENU', 'MULTICOL', 'OL', 'P', 'PLAINTEXT', 'PRE',
1426 'TABLE', 'UL', 'XMP',
1428 'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH',
1431 'DIR', 'MENU', 'OL', 'UL', 'DL',
1437 Roo.HtmlEditorCore.black = [
1438 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1440 'BASE', 'BASEFONT', 'BGSOUND', 'BLINK', 'BODY',
1441 'FRAME', 'FRAMESET', 'HEAD', 'HTML', 'ILAYER',
1442 'IFRAME', 'LAYER', 'LINK', 'META', 'OBJECT',
1443 'SCRIPT', 'STYLE' ,'TITLE', 'XML',
1444 //'FONT' // CLEAN LATER..
1445 'COLGROUP', 'COL' // messy tables.
1448 Roo.HtmlEditorCore.clean = [ // ?? needed???
1449 'SCRIPT', 'STYLE', 'TITLE', 'XML'
1451 Roo.HtmlEditorCore.tag_remove = [
1456 Roo.HtmlEditorCore.ablack = [
1460 Roo.HtmlEditorCore.aclean = [
1461 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1465 Roo.HtmlEditorCore.pwhite= [
1466 'http', 'https', 'mailto'
1469 // white listed style attributes.
1470 Roo.HtmlEditorCore.cwhite= [
1471 // 'text-align', /// default is to allow most things..
1477 // black listed style attributes.
1478 Roo.HtmlEditorCore.cblack= [
1479 // 'font-size' -- this can be set by the project