1 //<script type="text/javascript">
4 * Based Ext JS Library 1.1.1
5 * Copyright(c) 2006-2007, Ext JS, LLC.
11 * @class Roo.HtmlEditorCore
12 * @extends Roo.Component
13 * Provides a the editing component for the HTML editors in Roo. (bootstrap and Roo.form)
15 * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
18 Roo.HtmlEditorCore = function(config){
21 Roo.HtmlEditorCore.superclass.constructor.call(this, config);
27 * Fires when the editor is fully initialized (including the iframe)
28 * @param {Roo.HtmlEditorCore} this
33 * Fires when the editor is first receives the focus. Any insertion must wait
34 * until after this event.
35 * @param {Roo.HtmlEditorCore} this
40 * Fires before the textarea is updated with content from the editor iframe. Return false
42 * @param {Roo.HtmlEditorCore} this
43 * @param {String} html
48 * Fires before the iframe editor is updated with content from the textarea. Return false
50 * @param {Roo.HtmlEditorCore} this
51 * @param {String} html
56 * Fires when the textarea is updated with content from the editor iframe.
57 * @param {Roo.HtmlEditorCore} this
58 * @param {String} html
63 * Fires when the iframe editor is updated with content from the textarea.
64 * @param {Roo.HtmlEditorCore} this
65 * @param {String} html
71 * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
72 * @param {Roo.HtmlEditorCore} this
78 // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
80 // defaults : white / black...
81 this.applyBlacklists();
88 Roo.extend(Roo.HtmlEditorCore, Roo.Component, {
92 * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field
98 * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
103 * @cfg {Number} height (in pixels)
107 * @cfg {Number} width (in pixels)
112 * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
118 * @cfg {boolean} allowComments - default false - allow comments in HTML source - by default they are stripped - if you are editing email you may need this.
120 allowComments: false,
124 // private properties
125 validationEvent : false,
129 sourceEditMode : false,
130 onFocus : Roo.emptyFn,
136 // blacklist + whitelisted elements..
145 * Protected method that will not generally be called directly. It
146 * is called when the editor initializes the iframe with HTML contents. Override this method if you
147 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
149 getDocMarkup : function(){
153 // inherit styels from page...??
154 if (this.stylesheets === false) {
156 Roo.get(document.head).select('style').each(function(node) {
157 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
160 Roo.get(document.head).select('link').each(function(node) {
161 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
164 } else if (!this.stylesheets.length) {
166 st = '<style type="text/css">' +
167 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
170 for (var i in this.stylesheets) {
171 if (typeof(this.stylesheets[i]) != 'string') {
174 st += '<link rel="stylesheet" href="' + this.stylesheets[i] +'" type="text/css">';
179 st += '<style type="text/css">' +
180 'IMG { cursor: pointer } ' +
183 var cls = 'roo-htmleditor-body';
185 if(this.bodyCls.length){
186 cls += ' ' + this.bodyCls;
189 return '<html><head>' + st +
190 //<style type="text/css">' +
191 //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
193 ' </head><body contenteditable="true" data-enable-grammerly="true" class="' + cls + '"></body></html>';
197 onRender : function(ct, position)
200 //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
201 this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
204 this.el.dom.style.border = '0 none';
205 this.el.dom.setAttribute('tabIndex', -1);
206 this.el.addClass('x-hidden hide');
210 if(Roo.isIE){ // fix IE 1px bogus margin
211 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
215 this.frameId = Roo.id();
219 var iframe = this.owner.wrap.createChild({
221 cls: 'form-control', // bootstrap..
225 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
230 this.iframe = iframe.dom;
234 this.doc.designMode = 'on';
237 this.doc.write(this.getDocMarkup());
241 var task = { // must defer to wait for browser to be ready
243 //console.log("run task?" + this.doc.readyState);
245 if(this.doc.body || this.doc.readyState == 'complete'){
247 this.doc.designMode="on";
252 Roo.TaskMgr.stop(task);
253 this.initEditor.defer(10, this);
260 Roo.TaskMgr.start(task);
265 onResize : function(w, h)
267 Roo.log('resize: ' +w + ',' + h );
268 //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
272 if(typeof w == 'number'){
274 this.iframe.style.width = w + 'px';
276 if(typeof h == 'number'){
278 this.iframe.style.height = h + 'px';
280 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
287 * Toggles the editor between standard and source edit mode.
288 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
290 toggleSourceEdit : function(sourceEditMode){
292 this.sourceEditMode = sourceEditMode === true;
294 if(this.sourceEditMode){
296 Roo.get(this.iframe).addClass(['x-hidden','hide', 'd-none']); //FIXME - what's the BS styles for these
299 Roo.get(this.iframe).removeClass(['x-hidden','hide', 'd-none']);
300 //this.iframe.className = '';
303 //this.setSize(this.owner.wrap.getSize());
304 //this.fireEvent('editmodechange', this, this.sourceEditMode);
311 * Protected method that will not generally be called directly. If you need/want
312 * custom HTML cleanup, this is the method you should override.
313 * @param {String} html The HTML to be cleaned
314 * return {String} The cleaned HTML
316 cleanHtml : function(html){
319 if(Roo.isSafari){ // strip safari nonsense
320 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
323 if(html == ' '){
330 * HTML Editor -> Textarea
331 * Protected method that will not generally be called directly. Syncs the contents
332 * of the editor iframe with the textarea.
334 syncValue : function()
336 Roo.log("HtmlEditorCore:syncValue (EDITOR->TEXT)");
337 if(this.initialized){
339 this.undoManager.addEvent();
342 var bd = (this.doc.body || this.doc.documentElement);
343 //this.cleanUpPaste(); -- this is done else where and causes havoc..
345 // not sure if this is really the place for this
346 // the blocks are synced occasionaly - since we currently dont add listeners on the blocks
347 // this has to update attributes that get duped.. like alt and caption..
350 //Roo.each(Roo.get(this.doc.body).query('*[data-block]'), function(e) {
351 // Roo.htmleditor.Block.factory(e);
355 var div = document.createElement('div');
356 div.innerHTML = bd.innerHTML;
357 // remove content editable. (blocks)
360 new Roo.htmleditor.FilterAttributes({node : div, attrib_black: [ 'contenteditable' ] });
362 var html = div.innerHTML;
364 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
365 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
367 html = '<div style="'+m[0]+'">' + html + '</div>';
370 html = this.cleanHtml(html);
371 // fix up the special chars.. normaly like back quotes in word...
372 // however we do not want to do this with chinese..
373 html = html.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u0080-\uFFFF]/g, function(match) {
375 var cc = match.charCodeAt();
377 // Get the character value, handling surrogate pairs
378 if (match.length == 2) {
379 // It's a surrogate pair, calculate the Unicode code point
380 var high = match.charCodeAt(0) - 0xD800;
381 var low = match.charCodeAt(1) - 0xDC00;
382 cc = (high * 0x400) + low + 0x10000;
384 (cc >= 0x4E00 && cc < 0xA000 ) ||
385 (cc >= 0x3400 && cc < 0x4E00 ) ||
386 (cc >= 0xf900 && cc < 0xfb00 )
391 // No, use a numeric entity. Here we brazenly (and possibly mistakenly)
392 return "&#" + cc + ";";
399 if(this.owner.fireEvent('beforesync', this, html) !== false){
400 this.el.dom.value = html;
401 this.owner.fireEvent('sync', this, html);
407 * TEXTAREA -> EDITABLE
408 * Protected method that will not generally be called directly. Pushes the value of the textarea
409 * into the iframe editor.
411 pushValue : function()
413 Roo.log("HtmlEditorCore:pushValue (TEXT->EDITOR)");
414 if(this.initialized){
415 var v = this.el.dom.value.trim();
418 if(this.owner.fireEvent('beforepush', this, v) !== false){
419 var d = (this.doc.body || this.doc.documentElement);
422 this.el.dom.value = d.innerHTML;
423 this.owner.fireEvent('push', this, v);
426 Roo.each(Roo.get(this.doc.body).query('*[data-block]'), function(e) {
428 Roo.htmleditor.Block.factory(e);
431 var lc = this.doc.body.lastChild;
432 if (lc && lc.nodeType == 1 && lc.getAttribute("contenteditable") == "false") {
433 // add an extra line at the end.
434 this.doc.body.appendChild(this.doc.createElement('br'));
442 deferFocus : function(){
443 this.focus.defer(10, this);
448 if(this.win && !this.sourceEditMode){
455 assignDocWin: function()
457 var iframe = this.iframe;
460 this.doc = iframe.contentWindow.document;
461 this.win = iframe.contentWindow;
463 // if (!Roo.get(this.frameId)) {
466 // this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
467 // this.win = Roo.get(this.frameId).dom.contentWindow;
469 if (!Roo.get(this.frameId) && !iframe.contentDocument) {
473 this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
474 this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
479 initEditor : function(){
480 //console.log("INIT EDITOR");
485 this.doc.designMode="on";
487 this.doc.write(this.getDocMarkup());
490 var dbody = (this.doc.body || this.doc.documentElement);
491 //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
492 // this copies styles from the containing element into thsi one..
493 // not sure why we need all of this..
494 //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
496 //var ss = this.el.getStyles( 'background-image', 'background-repeat');
497 //ss['background-attachment'] = 'fixed'; // w3c
498 dbody.bgProperties = 'fixed'; // ie
499 //Roo.DomHelper.applyStyles(dbody, ss);
500 Roo.EventManager.on(this.doc, {
501 //'mousedown': this.onEditorEvent,
502 'mouseup': this.onEditorEvent,
503 'dblclick': this.onEditorEvent,
504 'click': this.onEditorEvent,
505 'keyup': this.onEditorEvent,
510 Roo.EventManager.on(this.doc, {
511 'paste': this.onPasteEvent,
515 Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
517 if(Roo.isIE || Roo.isSafari || Roo.isOpera){
518 Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
520 this.initialized = true;
523 // initialize special key events - enter
524 new Roo.htmleditor.KeyEnter({core : this});
528 this.owner.fireEvent('initialize', this);
532 onPasteEvent : function(e,v)
534 // I think we better assume paste is going to be a dirty load of rubish from word..
536 // even pasting into a 'email version' of this widget will have to clean up that mess.
537 var cd = (e.browserEvent.clipboardData || window.clipboardData);
539 // check what type of paste - if it's an image, then handle it differently.
540 if (cd.files.length > 0) {
542 var urlAPI = (window.createObjectURL && window) ||
543 (window.URL && URL.revokeObjectURL && URL) ||
544 (window.webkitURL && webkitURL);
546 var url = urlAPI.createObjectURL( cd.files[0]);
547 this.insertAtCursor('<img src=" + url + ">');
551 var html = cd.getData('text/html'); // clipboard event
552 var parser = new Roo.rtf.Parser(cd.getData('text/rtf'));
553 var images = parser.doc ? parser.doc.getElementsByType('pict') : [];
557 images = images.filter(function(g) { return !g.path.match(/^rtf\/(head|pgdsctbl|listtable)/); }) // ignore headers
558 .map(function(g) { return g.toDataURL(); });
561 html = this.cleanWordChars(html);
563 var d = (new DOMParser().parseFromString(html, 'text/html')).body;
565 if (images.length > 0) {
566 Roo.each(d.getElementsByTagName('img'), function(img, i) {
567 img.setAttribute('src', images[i]);
572 new Roo.htmleditor.FilterStyleToTag({ node : d });
573 new Roo.htmleditor.FilterAttributes({
575 attrib_white : ['href', 'src', 'name', 'align'],
576 attrib_clean : ['href', 'src' ]
578 new Roo.htmleditor.FilterBlack({ node : d, tag : this.black});
580 new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT' ]} );
581 new Roo.htmleditor.FilterParagraph({ node : d });
582 new Roo.htmleditor.FilterSpan({ node : d });
583 new Roo.htmleditor.FilterLongBr({ node : d });
587 this.insertAtCursor(d.innerHTML);
591 // default behaveiour should be our local cleanup paste? (optional?)
592 // for simple editor - we want to hammer the paste and get rid of everything... - so over-rideable..
593 //this.owner.fireEvent('paste', e, v);
596 onDestroy : function(){
602 //for (var i =0; i < this.toolbars.length;i++) {
603 // // fixme - ask toolbars for heights?
604 // this.toolbars[i].onDestroy();
607 //this.wrap.dom.innerHTML = '';
608 //this.wrap.remove();
613 onFirstFocus : function(){
616 this.undoManager = new Roo.lib.UndoManager(100,(this.doc.body || this.doc.documentElement));
618 this.activated = true;
621 if(Roo.isGecko){ // prevent silly gecko errors
623 var s = this.win.getSelection();
624 if(!s.focusNode || s.focusNode.nodeType != 3){
625 var r = s.getRangeAt(0);
626 r.selectNodeContents((this.doc.body || this.doc.documentElement));
631 this.execCmd('useCSS', true);
632 this.execCmd('styleWithCSS', false);
635 this.owner.fireEvent('activate', this);
639 adjustFont: function(btn){
640 var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
641 //if(Roo.isSafari){ // safari
644 var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
645 if(Roo.isSafari){ // safari
646 var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
647 v = (v < 10) ? 10 : v;
648 v = (v > 48) ? 48 : v;
649 v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
654 v = Math.max(1, v+adjust);
656 this.execCmd('FontSize', v );
659 onEditorEvent : function(e)
661 this.owner.fireEvent('editorevent', this, e);
662 // this.updateToolbar();
663 this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
666 insertTag : function(tg)
668 // could be a bit smarter... -> wrap the current selected tRoo..
669 if (tg.toLowerCase() == 'span' ||
670 tg.toLowerCase() == 'code' ||
671 tg.toLowerCase() == 'sup' ||
672 tg.toLowerCase() == 'sub'
675 range = this.createRange(this.getSelection());
676 var wrappingNode = this.doc.createElement(tg.toLowerCase());
677 wrappingNode.appendChild(range.extractContents());
678 range.insertNode(wrappingNode);
685 this.execCmd("formatblock", tg);
686 this.undoManager.addEvent();
689 insertText : function(txt)
693 var range = this.createRange();
694 range.deleteContents();
695 //alert(Sender.getAttribute('label'));
697 range.insertNode(this.doc.createTextNode(txt));
698 this.undoManager.addEvent();
704 * Executes a Midas editor command on the editor document and performs necessary focus and
705 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
706 * @param {String} cmd The Midas command
707 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
709 relayCmd : function(cmd, value){
711 this.execCmd(cmd, value);
712 this.owner.fireEvent('editorevent', this);
713 //this.updateToolbar();
714 this.owner.deferFocus();
718 * Executes a Midas editor command directly on the editor document.
719 * For visual commands, you should use {@link #relayCmd} instead.
720 * <b>This should only be called after the editor is initialized.</b>
721 * @param {String} cmd The Midas command
722 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
724 execCmd : function(cmd, value){
725 this.doc.execCommand(cmd, false, value === undefined ? null : value);
732 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
734 * @param {String} text | dom node..
736 insertAtCursor : function(text)
743 if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
747 // from jquery ui (MIT licenced)
751 if (win.getSelection && win.getSelection().getRangeAt) {
753 // delete the existing?
755 this.createRange(this.getSelection()).deleteContents();
756 range = win.getSelection().getRangeAt(0);
757 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
758 range.insertNode(node);
759 range = range.cloneRange();
760 range.collapse(false);
762 win.getSelection().removeAllRanges();
763 win.getSelection().addRange(range);
767 } else if (win.document.selection && win.document.selection.createRange) {
768 // no firefox support
769 var txt = typeof(text) == 'string' ? text : text.outerHTML;
770 win.document.selection.createRange().pasteHTML(txt);
773 // no firefox support
774 var txt = typeof(text) == 'string' ? text : text.outerHTML;
775 this.execCmd('InsertHTML', txt);
783 mozKeyPress : function(e){
785 var c = e.getCharCode(), cmd;
788 c = String.fromCharCode(c).toLowerCase();
802 // this.cleanUpPaste.defer(100, this);
818 fixKeys : function(){ // load time branching for fastest keydown performance
821 var k = e.getKey(), r;
824 r = this.doc.selection.createRange();
827 r.pasteHTML('    ');
834 r = this.doc.selection.createRange();
836 var target = r.parentElement();
837 if(!target || target.tagName.toLowerCase() != 'li'){
839 r.pasteHTML('<br/>');
845 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
846 // this.cleanUpPaste.defer(100, this);
852 }else if(Roo.isOpera){
858 this.execCmd('InsertHTML','    ');
861 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
862 // this.cleanUpPaste.defer(100, this);
867 }else if(Roo.isSafari){
873 this.execCmd('InsertText','\t');
877 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
878 // this.cleanUpPaste.defer(100, this);
886 getAllAncestors: function()
888 var p = this.getSelectedNode();
891 a.push(p); // push blank onto stack..
892 p = this.getParentElement();
896 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
900 a.push(this.doc.body);
907 getSelection : function()
910 return Roo.isIE ? this.doc.selection : this.win.getSelection();
914 * @param {DomElement} node the node to select
916 selectNode : function(node)
919 var nodeRange = node.ownerDocument.createRange();
921 nodeRange.selectNode(node);
923 nodeRange.selectNodeContents(node);
925 //nodeRange.collapse(true);
926 var s = this.win.getSelection();
928 s.addRange(nodeRange);
931 getSelectedNode: function()
933 // this may only work on Gecko!!!
935 // should we cache this!!!!
940 var range = this.createRange(this.getSelection()).cloneRange();
943 var parent = range.parentElement();
945 var testRange = range.duplicate();
946 testRange.moveToElementText(parent);
947 if (testRange.inRange(range)) {
950 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
953 parent = parent.parentElement;
958 // is ancestor a text element.
959 var ac = range.commonAncestorContainer;
960 if (ac.nodeType == 3) {
964 var ar = ac.childNodes;
967 var other_nodes = [];
968 var has_other_nodes = false;
969 for (var i=0;i<ar.length;i++) {
970 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
973 // fullly contained node.
975 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
980 // probably selected..
981 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
982 other_nodes.push(ar[i]);
986 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
991 has_other_nodes = true;
993 if (!nodes.length && other_nodes.length) {
996 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
1002 createRange: function(sel)
1004 // this has strange effects when using with
1005 // top toolbar - not sure if it's a great idea.
1006 //this.editor.contentWindow.focus();
1007 if (typeof sel != "undefined") {
1009 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
1011 return this.doc.createRange();
1014 return this.doc.createRange();
1017 getParentElement: function()
1020 this.assignDocWin();
1021 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
1023 var range = this.createRange(sel);
1026 var p = range.commonAncestorContainer;
1027 while (p.nodeType == 3) { // text node
1038 * Range intersection.. the hard stuff...
1042 * [ -- selected range --- ]
1046 * if end is before start or hits it. fail.
1047 * if start is after end or hits it fail.
1049 * if either hits (but other is outside. - then it's not
1055 // @see http://www.thismuchiknow.co.uk/?p=64.
1056 rangeIntersectsNode : function(range, node)
1058 var nodeRange = node.ownerDocument.createRange();
1060 nodeRange.selectNode(node);
1062 nodeRange.selectNodeContents(node);
1065 var rangeStartRange = range.cloneRange();
1066 rangeStartRange.collapse(true);
1068 var rangeEndRange = range.cloneRange();
1069 rangeEndRange.collapse(false);
1071 var nodeStartRange = nodeRange.cloneRange();
1072 nodeStartRange.collapse(true);
1074 var nodeEndRange = nodeRange.cloneRange();
1075 nodeEndRange.collapse(false);
1077 return rangeStartRange.compareBoundaryPoints(
1078 Range.START_TO_START, nodeEndRange) == -1 &&
1079 rangeEndRange.compareBoundaryPoints(
1080 Range.START_TO_START, nodeStartRange) == 1;
1084 rangeCompareNode : function(range, node)
1086 var nodeRange = node.ownerDocument.createRange();
1088 nodeRange.selectNode(node);
1090 nodeRange.selectNodeContents(node);
1094 range.collapse(true);
1096 nodeRange.collapse(true);
1098 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1099 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
1101 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1103 var nodeIsBefore = ss == 1;
1104 var nodeIsAfter = ee == -1;
1106 if (nodeIsBefore && nodeIsAfter) {
1109 if (!nodeIsBefore && nodeIsAfter) {
1110 return 1; //right trailed.
1113 if (nodeIsBefore && !nodeIsAfter) {
1114 return 2; // left trailed.
1120 cleanWordChars : function(input) {// change the chars to hex code
1123 [ 8211, "–" ],
1124 [ 8212, "—" ],
1133 Roo.each(swapCodes, function(sw) {
1134 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1136 output = output.replace(swapper, sw[1]);
1146 cleanUpChild : function (node)
1149 new Roo.htmleditor.FilterComment({node : node});
1150 new Roo.htmleditor.FilterAttributes({
1152 attrib_black : this.ablack,
1153 attrib_clean : this.aclean,
1154 style_white : this.cwhite,
1155 style_black : this.cblack
1157 new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
1158 new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
1164 * Clean up MS wordisms...
1165 * @deprecated - use filter directly
1167 cleanWord : function(node)
1169 new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
1176 * @deprecated - use filters
1178 cleanTableWidths : function(node)
1180 new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
1187 applyBlacklists : function()
1189 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1190 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1192 this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean : Roo.HtmlEditorCore.aclean;
1193 this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack : Roo.HtmlEditorCore.ablack;
1194 this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove : Roo.HtmlEditorCore.tag_remove;
1198 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1199 if (b.indexOf(tag) > -1) {
1202 this.white.push(tag);
1206 Roo.each(w, function(tag) {
1207 if (b.indexOf(tag) > -1) {
1210 if (this.white.indexOf(tag) > -1) {
1213 this.white.push(tag);
1218 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1219 if (w.indexOf(tag) > -1) {
1222 this.black.push(tag);
1226 Roo.each(b, function(tag) {
1227 if (w.indexOf(tag) > -1) {
1230 if (this.black.indexOf(tag) > -1) {
1233 this.black.push(tag);
1238 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1239 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1243 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1244 if (b.indexOf(tag) > -1) {
1247 this.cwhite.push(tag);
1251 Roo.each(w, function(tag) {
1252 if (b.indexOf(tag) > -1) {
1255 if (this.cwhite.indexOf(tag) > -1) {
1258 this.cwhite.push(tag);
1263 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1264 if (w.indexOf(tag) > -1) {
1267 this.cblack.push(tag);
1271 Roo.each(b, function(tag) {
1272 if (w.indexOf(tag) > -1) {
1275 if (this.cblack.indexOf(tag) > -1) {
1278 this.cblack.push(tag);
1283 setStylesheets : function(stylesheets)
1285 if(typeof(stylesheets) == 'string'){
1286 Roo.get(this.iframe.contentDocument.head).createChild({
1297 Roo.each(stylesheets, function(s) {
1302 Roo.get(_this.iframe.contentDocument.head).createChild({
1313 removeStylesheets : function()
1317 Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1322 setStyle : function(style)
1324 Roo.get(this.iframe.contentDocument.head).createChild({
1333 // hide stuff that is not compatible
1351 * @cfg {String} fieldClass @hide
1354 * @cfg {String} focusClass @hide
1357 * @cfg {String} autoCreate @hide
1360 * @cfg {String} inputType @hide
1363 * @cfg {String} invalidClass @hide
1366 * @cfg {String} invalidText @hide
1369 * @cfg {String} msgFx @hide
1372 * @cfg {String} validateOnBlur @hide
1376 Roo.HtmlEditorCore.white = [
1377 'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
1379 'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD', 'DIR', 'DIV',
1380 'DL', 'DT', 'H1', 'H2', 'H3', 'H4',
1381 'H5', 'H6', 'HR', 'ISINDEX', 'LISTING', 'MARQUEE',
1382 'MENU', 'MULTICOL', 'OL', 'P', 'PLAINTEXT', 'PRE',
1383 'TABLE', 'UL', 'XMP',
1385 'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH',
1388 'DIR', 'MENU', 'OL', 'UL', 'DL',
1394 Roo.HtmlEditorCore.black = [
1395 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1397 'BASE', 'BASEFONT', 'BGSOUND', 'BLINK', 'BODY',
1398 'FRAME', 'FRAMESET', 'HEAD', 'HTML', 'ILAYER',
1399 'IFRAME', 'LAYER', 'LINK', 'META', 'OBJECT',
1400 'SCRIPT', 'STYLE' ,'TITLE', 'XML',
1401 //'FONT' // CLEAN LATER..
1402 'COLGROUP', 'COL' // messy tables.
1405 Roo.HtmlEditorCore.clean = [ // ?? needed???
1406 'SCRIPT', 'STYLE', 'TITLE', 'XML'
1408 Roo.HtmlEditorCore.tag_remove = [
1413 Roo.HtmlEditorCore.ablack = [
1417 Roo.HtmlEditorCore.aclean = [
1418 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1422 Roo.HtmlEditorCore.pwhite= [
1423 'http', 'https', 'mailto'
1426 // white listed style attributes.
1427 Roo.HtmlEditorCore.cwhite= [
1428 // 'text-align', /// default is to allow most things..
1434 // black listed style attributes.
1435 Roo.HtmlEditorCore.cblack= [
1436 // 'font-size' -- this can be set by the project