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)
918 var nodeRange = node.ownerDocument.createRange();
920 nodeRange.selectNode(node);
922 nodeRange.selectNodeContents(node);
924 //nodeRange.collapse(true);
925 var s = this.win.getSelection();
927 s.addRange(nodeRange);
930 getSelectedNode: function()
932 // this may only work on Gecko!!!
934 // should we cache this!!!!
939 var range = this.createRange(this.getSelection()).cloneRange();
942 var parent = range.parentElement();
944 var testRange = range.duplicate();
945 testRange.moveToElementText(parent);
946 if (testRange.inRange(range)) {
949 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
952 parent = parent.parentElement;
957 // is ancestor a text element.
958 var ac = range.commonAncestorContainer;
959 if (ac.nodeType == 3) {
963 var ar = ac.childNodes;
966 var other_nodes = [];
967 var has_other_nodes = false;
968 for (var i=0;i<ar.length;i++) {
969 if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
972 // fullly contained node.
974 if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
979 // probably selected..
980 if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
981 other_nodes.push(ar[i]);
985 if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
990 has_other_nodes = true;
992 if (!nodes.length && other_nodes.length) {
995 if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
1001 createRange: function(sel)
1003 // this has strange effects when using with
1004 // top toolbar - not sure if it's a great idea.
1005 //this.editor.contentWindow.focus();
1006 if (typeof sel != "undefined") {
1008 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
1010 return this.doc.createRange();
1013 return this.doc.createRange();
1016 getParentElement: function()
1019 this.assignDocWin();
1020 var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
1022 var range = this.createRange(sel);
1025 var p = range.commonAncestorContainer;
1026 while (p.nodeType == 3) { // text node
1037 * Range intersection.. the hard stuff...
1041 * [ -- selected range --- ]
1045 * if end is before start or hits it. fail.
1046 * if start is after end or hits it fail.
1048 * if either hits (but other is outside. - then it's not
1054 // @see http://www.thismuchiknow.co.uk/?p=64.
1055 rangeIntersectsNode : function(range, node)
1057 var nodeRange = node.ownerDocument.createRange();
1059 nodeRange.selectNode(node);
1061 nodeRange.selectNodeContents(node);
1064 var rangeStartRange = range.cloneRange();
1065 rangeStartRange.collapse(true);
1067 var rangeEndRange = range.cloneRange();
1068 rangeEndRange.collapse(false);
1070 var nodeStartRange = nodeRange.cloneRange();
1071 nodeStartRange.collapse(true);
1073 var nodeEndRange = nodeRange.cloneRange();
1074 nodeEndRange.collapse(false);
1076 return rangeStartRange.compareBoundaryPoints(
1077 Range.START_TO_START, nodeEndRange) == -1 &&
1078 rangeEndRange.compareBoundaryPoints(
1079 Range.START_TO_START, nodeStartRange) == 1;
1083 rangeCompareNode : function(range, node)
1085 var nodeRange = node.ownerDocument.createRange();
1087 nodeRange.selectNode(node);
1089 nodeRange.selectNodeContents(node);
1093 range.collapse(true);
1095 nodeRange.collapse(true);
1097 var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
1098 var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
1100 //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
1102 var nodeIsBefore = ss == 1;
1103 var nodeIsAfter = ee == -1;
1105 if (nodeIsBefore && nodeIsAfter) {
1108 if (!nodeIsBefore && nodeIsAfter) {
1109 return 1; //right trailed.
1112 if (nodeIsBefore && !nodeIsAfter) {
1113 return 2; // left trailed.
1119 cleanWordChars : function(input) {// change the chars to hex code
1122 [ 8211, "–" ],
1123 [ 8212, "—" ],
1132 Roo.each(swapCodes, function(sw) {
1133 var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
1135 output = output.replace(swapper, sw[1]);
1145 cleanUpChild : function (node)
1148 new Roo.htmleditor.FilterComment({node : node});
1149 new Roo.htmleditor.FilterAttributes({
1151 attrib_black : this.ablack,
1152 attrib_clean : this.aclean,
1153 style_white : this.cwhite,
1154 style_black : this.cblack
1156 new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
1157 new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
1163 * Clean up MS wordisms...
1164 * @deprecated - use filter directly
1166 cleanWord : function(node)
1168 new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
1175 * @deprecated - use filters
1177 cleanTableWidths : function(node)
1179 new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
1186 applyBlacklists : function()
1188 var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
1189 var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
1191 this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean : Roo.HtmlEditorCore.aclean;
1192 this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack : Roo.HtmlEditorCore.ablack;
1193 this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove : Roo.HtmlEditorCore.tag_remove;
1197 Roo.each(Roo.HtmlEditorCore.white, function(tag) {
1198 if (b.indexOf(tag) > -1) {
1201 this.white.push(tag);
1205 Roo.each(w, function(tag) {
1206 if (b.indexOf(tag) > -1) {
1209 if (this.white.indexOf(tag) > -1) {
1212 this.white.push(tag);
1217 Roo.each(Roo.HtmlEditorCore.black, function(tag) {
1218 if (w.indexOf(tag) > -1) {
1221 this.black.push(tag);
1225 Roo.each(b, function(tag) {
1226 if (w.indexOf(tag) > -1) {
1229 if (this.black.indexOf(tag) > -1) {
1232 this.black.push(tag);
1237 w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
1238 b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
1242 Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
1243 if (b.indexOf(tag) > -1) {
1246 this.cwhite.push(tag);
1250 Roo.each(w, function(tag) {
1251 if (b.indexOf(tag) > -1) {
1254 if (this.cwhite.indexOf(tag) > -1) {
1257 this.cwhite.push(tag);
1262 Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
1263 if (w.indexOf(tag) > -1) {
1266 this.cblack.push(tag);
1270 Roo.each(b, function(tag) {
1271 if (w.indexOf(tag) > -1) {
1274 if (this.cblack.indexOf(tag) > -1) {
1277 this.cblack.push(tag);
1282 setStylesheets : function(stylesheets)
1284 if(typeof(stylesheets) == 'string'){
1285 Roo.get(this.iframe.contentDocument.head).createChild({
1296 Roo.each(stylesheets, function(s) {
1301 Roo.get(_this.iframe.contentDocument.head).createChild({
1312 removeStylesheets : function()
1316 Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
1321 setStyle : function(style)
1323 Roo.get(this.iframe.contentDocument.head).createChild({
1332 // hide stuff that is not compatible
1350 * @cfg {String} fieldClass @hide
1353 * @cfg {String} focusClass @hide
1356 * @cfg {String} autoCreate @hide
1359 * @cfg {String} inputType @hide
1362 * @cfg {String} invalidClass @hide
1365 * @cfg {String} invalidText @hide
1368 * @cfg {String} msgFx @hide
1371 * @cfg {String} validateOnBlur @hide
1375 Roo.HtmlEditorCore.white = [
1376 'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
1378 'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD', 'DIR', 'DIV',
1379 'DL', 'DT', 'H1', 'H2', 'H3', 'H4',
1380 'H5', 'H6', 'HR', 'ISINDEX', 'LISTING', 'MARQUEE',
1381 'MENU', 'MULTICOL', 'OL', 'P', 'PLAINTEXT', 'PRE',
1382 'TABLE', 'UL', 'XMP',
1384 'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH',
1387 'DIR', 'MENU', 'OL', 'UL', 'DL',
1393 Roo.HtmlEditorCore.black = [
1394 // 'embed', 'object', // enable - backend responsiblity to clean thiese
1396 'BASE', 'BASEFONT', 'BGSOUND', 'BLINK', 'BODY',
1397 'FRAME', 'FRAMESET', 'HEAD', 'HTML', 'ILAYER',
1398 'IFRAME', 'LAYER', 'LINK', 'META', 'OBJECT',
1399 'SCRIPT', 'STYLE' ,'TITLE', 'XML',
1400 //'FONT' // CLEAN LATER..
1401 'COLGROUP', 'COL' // messy tables.
1404 Roo.HtmlEditorCore.clean = [ // ?? needed???
1405 'SCRIPT', 'STYLE', 'TITLE', 'XML'
1407 Roo.HtmlEditorCore.tag_remove = [
1412 Roo.HtmlEditorCore.ablack = [
1416 Roo.HtmlEditorCore.aclean = [
1417 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
1421 Roo.HtmlEditorCore.pwhite= [
1422 'http', 'https', 'mailto'
1425 // white listed style attributes.
1426 Roo.HtmlEditorCore.cwhite= [
1427 // 'text-align', /// default is to allow most things..
1433 // black listed style attributes.
1434 Roo.HtmlEditorCore.cblack= [
1435 // 'font-size' -- this can be set by the project