-/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
+/*
+ * - LGPL
+ *
+ * HtmlEditor
+ *
*/
+/**
+ * @class Roo.bootstrap.HtmlEditor
+ * @extends Roo.bootstrap.Component
+ * Bootstrap HtmlEditor class
+ * @constructor
+ * Create a new HtmlEditor
+ * @param {Object} config The config object
+ */
+
+Roo.bootstrap.HtmlEditor = function(config){
+ Roo.bootstrap.HtmlEditor.superclass.constructor.call(this, config);
+
+ this.addEvents({
+ /**
+ * @event initialize
+ * Fires when the editor is fully initialized (including the iframe)
+ * @param {HtmlEditor} this
+ */
+ initialize: true,
+ /**
+ * @event activate
+ * Fires when the editor is first receives the focus. Any insertion must wait
+ * until after this event.
+ * @param {HtmlEditor} this
+ */
+ activate: true,
+ /**
+ * @event beforesync
+ * Fires before the textarea is updated with content from the editor iframe. Return false
+ * to cancel the sync.
+ * @param {HtmlEditor} this
+ * @param {String} html
+ */
+ beforesync: true,
+ /**
+ * @event beforepush
+ * Fires before the iframe editor is updated with content from the textarea. Return false
+ * to cancel the push.
+ * @param {HtmlEditor} this
+ * @param {String} html
+ */
+ beforepush: true,
+ /**
+ * @event sync
+ * Fires when the textarea is updated with content from the editor iframe.
+ * @param {HtmlEditor} this
+ * @param {String} html
+ */
+ sync: true,
+ /**
+ * @event push
+ * Fires when the iframe editor is updated with content from the textarea.
+ * @param {HtmlEditor} this
+ * @param {String} html
+ */
+ push: true,
+ /**
+ * @event editmodechange
+ * Fires when the editor switches edit modes
+ * @param {HtmlEditor} this
+ * @param {Boolean} sourceEdit True if source edit, false if standard editing.
+ */
+ editmodechange: true,
+ /**
+ * @event editorevent
+ * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
+ * @param {HtmlEditor} this
+ */
+ editorevent: true
+ });
+
+};
+
+
+Roo.extend(Roo.bootstrap.HtmlEditor, Roo.bootstrap.Component, {
+ /**
+ * @cfg {Array} toolbars Array of toolbars. - defaults to just the Standard one
+ */
+ toolbars : false,
+ /**
+ * @cfg {String} createLinkText The default text for the create link prompt
+ */
+ createLinkText : 'Please enter the URL for the link:',
+ /**
+ * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
+ */
+ defaultLinkValue : 'http:/'+'/',
+
+ /**
+ * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
+ * Roo.resizable.
+ */
+ resizable : false,
+ /**
+ * @cfg {Number} height (in pixels)
+ */
+ height: 300,
+ /**
+ * @cfg {Number} width (in pixels)
+ */
+ width: 500,
+
+ /**
+ * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
+ *
+ */
+ stylesheets: false,
+
+ // id of frame..
+ frameId: false,
+
+ // private properties
+ validationEvent : false,
+ deferHeight: true,
+ initialized : false,
+ activated : false,
+ sourceEditMode : false,
+ onFocus : Roo.emptyFn,
+ iframePad:3,
+ hideMode:'offsets',
+
+ inputEl: function ()
+ {
+ return this.el.select('textarea.form-control',true).first();
+ },
+
+ getAutoCreate : function()
+ {
+ var cfg = {
+ tag: "textarea",
+ cls: "form-control",
+ style:'width: ' + this.width + 'px;height: ' + this.height + 'px;',
+ autocomplete: "off"
+ }
+
+ return cfg;
+
+ }
+//
+// /**
+// * Protected method that will not generally be called directly. It
+// * is called when the editor creates its toolbar. Override this method if you need to
+// * add custom toolbar buttons.
+// * @param {Roo.bootstrap.HtmlEditor} editor
+// */
+// createToolbar : function(editor){
+// if (!editor.toolbars || !editor.toolbars.length) {
+// editor.toolbars = [ new Roo.bootstrap.HtmlEditor.ToolbarStandard() ]; // can be empty?
+// }
+//
+// for (var i =0 ; i < editor.toolbars.length;i++) {
+// editor.toolbars[i] = Roo.factory(
+// typeof(editor.toolbars[i]) == 'string' ?
+// { xtype: editor.toolbars[i]} : editor.toolbars[i],
+// Roo.bootstrap.HtmlEditor);
+// editor.toolbars[i].init(editor);
+// }
+//
+//
+// },
+//
+// /**
+// * Protected method that will not generally be called directly. It
+// * is called when the editor initializes the iframe with HTML contents. Override this method if you
+// * want to change the initialization markup of the iframe (e.g. to add stylesheets).
+// */
+// getDocMarkup : function(){
+// // body styles..
+// var st = '';
+// if (this.stylesheets === false) {
+//
+// Roo.get(document.head).select('style').each(function(node) {
+// st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
+// });
+//
+// Roo.get(document.head).select('link').each(function(node) {
+// st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
+// });
+//
+// } else if (!this.stylesheets.length) {
+// // simple..
+// st = '<style type="text/css">' +
+// 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
+// '</style>';
+// } else {
+// Roo.each(this.stylesheets, function(s) {
+// st += '<link rel="stylesheet" type="text/css" href="' + s +'" />'
+// });
+//
+// }
+//
+// st += '<style type="text/css">' +
+// 'IMG { cursor: pointer } ' +
+// '</style>';
+//
+//
+// return '<html><head>' + st +
+// //<style type="text/css">' +
+// //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
+// //'</style>' +
+// ' </head><body class="roo-htmleditor-body"></body></html>';
+// },
+//
+// // private
+// onRender : function(ct, position)
+// {
+// var _t = this;
+// Roo.bootstrap.HtmlEditor.superclass.onRender.call(this, ct, position);
+// this.inputEl().dom.style.border = '0 none';
+// this.inputEl().dom.setAttribute('tabIndex', -1);
+// this.inputEl().addClass('x-hidden');
+// if(Roo.isIE){ // fix IE 1px bogus margin
+// this.inputEl().applyStyles('margin-top:-1px;margin-bottom:-1px;')
+// }
+// this.wrap = this.inputEl().wrap({
+// cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
+// });
+//
+// if (this.resizable) {
+// this.resizeEl = new Roo.Resizable(this.wrap, {
+// pinned : true,
+// wrap: true,
+// dynamic : true,
+// minHeight : this.height,
+// height: this.height,
+// handles : this.resizable,
+// width: this.width,
+// listeners : {
+// resize : function(r, w, h) {
+// _t.onResize(w,h); // -something
+// }
+// }
+// });
+//
+// }
+//
+// this.frameId = Roo.id();
+//
+// this.createToolbar(this);
+//
+//
+//
+// var iframe = this.wrap.createChild({
+// tag: 'iframe',
+// id: this.frameId,
+// name: this.frameId,
+// frameBorder : 'no',
+// 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
+// }, this.inputEl()
+// );
+//
+// // console.log(iframe);
+// //this.wrap.dom.appendChild(iframe);
+//
+// this.iframe = iframe.dom;
+//
+// this.assignDocWin();
+//
+// this.doc.designMode = 'on';
+//
+// this.doc.open();
+// this.doc.write(this.getDocMarkup());
+// this.doc.close();
+//
+//
+// var task = { // must defer to wait for browser to be ready
+// run : function(){
+// //console.log("run task?" + this.doc.readyState);
+// this.assignDocWin();
+// if(this.doc.body || this.doc.readyState == 'complete'){
+// try {
+// this.doc.designMode="on";
+// } catch (e) {
+// return;
+// }
+// Roo.TaskMgr.stop(task);
+// this.initEditor.defer(10, this);
+// }
+// },
+// interval : 10,
+// duration:10000,
+// scope: this
+// };
+// Roo.TaskMgr.start(task);
+//
+// if(!this.width){
+// this.setSize(this.wrap.getSize());
+// }
+// if (this.resizeEl) {
+// this.resizeEl.resizeTo.defer(100, this.resizeEl,[ this.width,this.height ] );
+// // should trigger onReize..
+// }
+// },
+//
+// // private
+// onResize : function(w, h)
+// {
+// //Roo.log('resize: ' +w + ',' + h );
+// Roo.inputEl().HtmlEditor.superclass.onResize.apply(this, arguments);
+// if(this.inputEl() && this.iframe){
+// if(typeof w == 'number'){
+// var aw = w - this.wrap.getFrameWidth('lr');
+// this.inputEl().setWidth(this.adjustWidth('textarea', aw));
+// this.iframe.style.width = aw + 'px';
+// }
+// if(typeof h == 'number'){
+// var tbh = 0;
+// for (var i =0; i < this.toolbars.length;i++) {
+// // fixme - ask toolbars for heights?
+// tbh += this.toolbars[i].tb.el.getHeight();
+// if (this.toolbars[i].footer) {
+// tbh += this.toolbars[i].footer.el.getHeight();
+// }
+// }
+//
+//
+//
+//
+// var ah = h - this.wrap.getFrameWidth('tb') - tbh;// this.tb.el.getHeight();
+// ah -= 5; // knock a few pixes off for look..
+// this.inputEl().setHeight(this.adjustWidth('textarea', ah));
+// this.iframe.style.height = ah + 'px';
+// if(this.doc){
+// (this.doc.body || this.doc.documentElement).style.height = (ah - (this.iframePad*2)) + 'px';
+// }
+// }
+// }
+// },
+//
+// /**
+// * Toggles the editor between standard and source edit mode.
+// * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
+// */
+// toggleSourceEdit : function(sourceEditMode){
+//
+// this.sourceEditMode = sourceEditMode === true;
+//
+// if(this.sourceEditMode){
+//// Roo.log('in');
+//// Roo.log(this.syncValue());
+// this.syncValue();
+// this.iframe.className = 'x-hidden';
+// this.inputEl().removeClass('x-hidden');
+// this.inputEl().dom.removeAttribute('tabIndex');
+// this.inputEl().focus();
+// }else{
+//// Roo.log('out')
+//// Roo.log(this.pushValue());
+// this.pushValue();
+// this.iframe.className = '';
+// this.inputEl().addClass('x-hidden');
+// this.inputEl().dom.setAttribute('tabIndex', -1);
+// this.deferFocus();
+// }
+// this.setSize(this.wrap.getSize());
+// this.fireEvent('editmodechange', this, this.sourceEditMode);
+// },
+//
+// // private used internally
+// createLink : function(){
+// var url = prompt(this.createLinkText, this.defaultLinkValue);
+// if(url && url != 'http:/'+'/'){
+// this.relayCmd('createlink', url);
+// }
+// },
+//
+// // private (for BoxComponent)
+// adjustSize : Roo.BoxComponent.prototype.adjustSize,
+//
+// // private (for BoxComponent)
+// getResizeEl : function(){
+// return this.wrap;
+// },
+//
+// // private (for BoxComponent)
+// getPositionEl : function(){
+// return this.wrap;
+// },
+//
+// // private
+// initEvents : function(){
+// this.originalValue = this.getValue();
+// },
+//
+// /**
+// * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
+// * @method
+// */
+// markInvalid : Roo.emptyFn,
+// /**
+// * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
+// * @method
+// */
+// clearInvalid : Roo.emptyFn,
+//
+// setValue : function(v){
+// Roo.bootstrap.HtmlEditor.superclass.setValue.call(this, v);
+// this.pushValue();
+// },
+//
+// /**
+// * Protected method that will not generally be called directly. If you need/want
+// * custom HTML cleanup, this is the method you should override.
+// * @param {String} html The HTML to be cleaned
+// * return {String} The cleaned HTML
+// */
+// cleanHtml : function(html){
+// html = String(html);
+// if(html.length > 5){
+// if(Roo.isSafari){ // strip safari nonsense
+// html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
+// }
+// }
+// if(html == ' '){
+// html = '';
+// }
+// return html;
+// },
+//
+// /**
+// * Protected method that will not generally be called directly. Syncs the contents
+// * of the editor iframe with the textarea.
+// */
+// syncValue : function(){
+// if(this.initialized){
+// var bd = (this.doc.body || this.doc.documentElement);
+// //this.cleanUpPaste(); -- this is done else where and causes havoc..
+// var html = bd.innerHTML;
+// if(Roo.isSafari){
+// var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
+// var m = bs.match(/text-align:(.*?);/i);
+// if(m && m[1]){
+// html = '<div style="'+m[0]+'">' + html + '</div>';
+// }
+// }
+// html = this.cleanHtml(html);
+// // fix up the special chars.. normaly like back quotes in word...
+// // however we do not want to do this with chinese..
+// html = html.replace(/([\x80-\uffff])/g, function (a, b) {
+// var cc = b.charCodeAt();
+// if (
+// (cc >= 0x4E00 && cc < 0xA000 ) ||
+// (cc >= 0x3400 && cc < 0x4E00 ) ||
+// (cc >= 0xf900 && cc < 0xfb00 )
+// ) {
+// return b;
+// }
+// return "&#"+cc+";"
+// });
+// if(this.fireEvent('beforesync', this, html) !== false){
+// this.inputEl().dom.value = html;
+// this.fireEvent('sync', this, html);
+// }
+// }
+// },
+//
+// /**
+// * Protected method that will not generally be called directly. Pushes the value of the textarea
+// * into the iframe editor.
+// */
+// pushValue : function(){
+// if(this.initialized){
+// var v = this.inputEl().dom.value;
+//
+// if(v.length < 1){
+// v = ' ';
+// }
+//
+// if(this.fireEvent('beforepush', this, v) !== false){
+// var d = (this.doc.body || this.doc.documentElement);
+// d.innerHTML = v;
+// this.cleanUpPaste();
+// this.inputEl().dom.value = d.innerHTML;
+// this.fireEvent('push', this, v);
+// }
+// }
+// },
+//
+// // private
+// deferFocus : function(){
+// this.focus.defer(10, this);
+// },
+//
+// // doc'ed in Field
+// focus : function(){
+// if(this.win && !this.sourceEditMode){
+// this.win.focus();
+// }else{
+// this.inputEl().focus();
+// }
+// },
+//
+// assignDocWin: function()
+// {
+// var iframe = this.iframe;
+//
+// if(Roo.isIE){
+// this.doc = iframe.contentWindow.document;
+// this.win = iframe.contentWindow;
+// } else {
+// if (!Roo.get(this.frameId)) {
+// return;
+// }
+// this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
+// this.win = Roo.get(this.frameId).dom.contentWindow;
+// }
+// },
+//
+// // private
+// initEditor : function(){
+// //console.log("INIT EDITOR");
+// this.assignDocWin();
+//
+//
+//
+// this.doc.designMode="on";
+// this.doc.open();
+// this.doc.write(this.getDocMarkup());
+// this.doc.close();
+//
+// var dbody = (this.doc.body || this.doc.documentElement);
+// //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
+// // this copies styles from the containing element into thsi one..
+// // not sure why we need all of this..
+// var ss = this.inputEl().getStyles('font-size', 'background-image', 'background-repeat');
+// ss['background-attachment'] = 'fixed'; // w3c
+// dbody.bgProperties = 'fixed'; // ie
+// Roo.DomHelper.applyStyles(dbody, ss);
+// Roo.EventManager.on(this.doc, {
+// //'mousedown': this.onEditorEvent,
+// 'mouseup': this.onEditorEvent,
+// 'dblclick': this.onEditorEvent,
+// 'click': this.onEditorEvent,
+// 'keyup': this.onEditorEvent,
+// buffer:100,
+// scope: this
+// });
+// if(Roo.isGecko){
+// Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
+// }
+// if(Roo.isIE || Roo.isSafari || Roo.isOpera){
+// Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
+// }
+// this.initialized = true;
+//
+// this.fireEvent('initialize', this);
+// this.pushValue();
+// },
+//
+// // private
+// onDestroy : function(){
+//
+//
+//
+// if(this.rendered){
+//
+// for (var i =0; i < this.toolbars.length;i++) {
+// // fixme - ask toolbars for heights?
+// this.toolbars[i].onDestroy();
+// }
+//
+// this.wrap.dom.innerHTML = '';
+// this.wrap.remove();
+// }
+// },
+//
+// // private
+// onFirstFocus : function(){
+//
+// this.assignDocWin();
+//
+//
+// this.activated = true;
+// for (var i =0; i < this.toolbars.length;i++) {
+// this.toolbars[i].onFirstFocus();
+// }
+//
+// if(Roo.isGecko){ // prevent silly gecko errors
+// this.win.focus();
+// var s = this.win.getSelection();
+// if(!s.focusNode || s.focusNode.nodeType != 3){
+// var r = s.getRangeAt(0);
+// r.selectNodeContents((this.doc.body || this.doc.documentElement));
+// r.collapse(true);
+// this.deferFocus();
+// }
+// try{
+// this.execCmd('useCSS', true);
+// this.execCmd('styleWithCSS', false);
+// }catch(e){}
+// }
+// this.fireEvent('activate', this);
+// },
+//
+// // private
+// adjustFont: function(btn){
+// var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
+// //if(Roo.isSafari){ // safari
+// // adjust *= 2;
+// // }
+// var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
+// if(Roo.isSafari){ // safari
+// var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
+// v = (v < 10) ? 10 : v;
+// v = (v > 48) ? 48 : v;
+// v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
+//
+// }
+//
+//
+// v = Math.max(1, v+adjust);
+//
+// this.execCmd('FontSize', v );
+// },
+//
+// onEditorEvent : function(e){
+// this.fireEvent('editorevent', this, e);
+// // this.updateToolbar();
+// this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
+// },
+//
+// insertTag : function(tg)
+// {
+// // could be a bit smarter... -> wrap the current selected tRoo..
+// if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
+//
+// range = this.createRange(this.getSelection());
+// var wrappingNode = this.doc.createElement(tg.toLowerCase());
+// wrappingNode.appendChild(range.extractContents());
+// range.insertNode(wrappingNode);
+//
+// return;
+//
+//
+//
+// }
+// this.execCmd("formatblock", tg);
+//
+// },
+//
+// insertText : function(txt)
+// {
+//
+//
+// var range = this.createRange();
+// range.deleteContents();
+// //alert(Sender.getAttribute('label'));
+//
+// range.insertNode(this.doc.createTextNode(txt));
+// } ,
+//
+// // private
+// relayBtnCmd : function(btn){
+// this.relayCmd(btn.cmd);
+// },
+//
+// /**
+// * Executes a Midas editor command on the editor document and performs necessary focus and
+// * toolbar updates. <b>This should only be called after the editor is initialized.</b>
+// * @param {String} cmd The Midas command
+// * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
+// */
+// relayCmd : function(cmd, value){
+// this.win.focus();
+// this.execCmd(cmd, value);
+// this.fireEvent('editorevent', this);
+// //this.updateToolbar();
+// this.deferFocus();
+// },
+//
+// /**
+// * Executes a Midas editor command directly on the editor document.
+// * For visual commands, you should use {@link #relayCmd} instead.
+// * <b>This should only be called after the editor is initialized.</b>
+// * @param {String} cmd The Midas command
+// * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
+// */
+// execCmd : function(cmd, value){
+// this.doc.execCommand(cmd, false, value === undefined ? null : value);
+// this.syncValue();
+// },
+//
+//
+//
+// /**
+// * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
+// * to insert tRoo.
+// * @param {String} text | dom node..
+// */
+// insertAtCursor : function(text)
+// {
+//
+//
+//
+// if(!this.activated){
+// return;
+// }
+// /*
+// if(Roo.isIE){
+// this.win.focus();
+// var r = this.doc.selection.createRange();
+// if(r){
+// r.collapse(true);
+// r.pasteHTML(text);
+// this.syncValue();
+// this.deferFocus();
+//
+// }
+// return;
+// }
+// */
+// if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
+// this.win.focus();
+//
+//
+// // from jquery ui (MIT licenced)
+// var range, node;
+// var win = this.win;
+//
+// if (win.getSelection && win.getSelection().getRangeAt) {
+// range = win.getSelection().getRangeAt(0);
+// node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
+// range.insertNode(node);
+// } else if (win.document.selection && win.document.selection.createRange) {
+// // no firefox support
+// var txt = typeof(text) == 'string' ? text : text.outerHTML;
+// win.document.selection.createRange().pasteHTML(txt);
+// } else {
+// // no firefox support
+// var txt = typeof(text) == 'string' ? text : text.outerHTML;
+// this.execCmd('InsertHTML', txt);
+// }
+//
+// this.syncValue();
+//
+// this.deferFocus();
+// }
+// },
+// // private
+// mozKeyPress : function(e){
+// if(e.ctrlKey){
+// var c = e.getCharCode(), cmd;
+//
+// if(c > 0){
+// c = String.fromCharCode(c).toLowerCase();
+// switch(c){
+// case 'b':
+// cmd = 'bold';
+// break;
+// case 'i':
+// cmd = 'italic';
+// break;
+//
+// case 'u':
+// cmd = 'underline';
+// break;
+//
+// case 'v':
+// this.cleanUpPaste.defer(100, this);
+// return;
+//
+// }
+// if(cmd){
+// this.win.focus();
+// this.execCmd(cmd);
+// this.deferFocus();
+// e.preventDefault();
+// }
+//
+// }
+// }
+// },
+//
+// // private
+// fixKeys : function(){ // load time branching for fastest keydown performance
+// if(Roo.isIE){
+// return function(e){
+// var k = e.getKey(), r;
+// if(k == e.TAB){
+// e.stopEvent();
+// r = this.doc.selection.createRange();
+// if(r){
+// r.collapse(true);
+// r.pasteHTML('    ');
+// this.deferFocus();
+// }
+// return;
+// }
+//
+// if(k == e.ENTER){
+// r = this.doc.selection.createRange();
+// if(r){
+// var target = r.parentElement();
+// if(!target || target.tagName.toLowerCase() != 'li'){
+// e.stopEvent();
+// r.pasteHTML('<br />');
+// r.collapse(false);
+// r.select();
+// }
+// }
+// }
+// if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
+// this.cleanUpPaste.defer(100, this);
+// return;
+// }
+//
+//
+// };
+// }else if(Roo.isOpera){
+// return function(e){
+// var k = e.getKey();
+// if(k == e.TAB){
+// e.stopEvent();
+// this.win.focus();
+// this.execCmd('InsertHTML','    ');
+// this.deferFocus();
+// }
+// if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
+// this.cleanUpPaste.defer(100, this);
+// return;
+// }
+//
+// };
+// }else if(Roo.isSafari){
+// return function(e){
+// var k = e.getKey();
+//
+// if(k == e.TAB){
+// e.stopEvent();
+// this.execCmd('InsertText','\t');
+// this.deferFocus();
+// return;
+// }
+// if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
+// this.cleanUpPaste.defer(100, this);
+// return;
+// }
+//
+// };
+// }
+// }(),
+//
+// getAllAncestors: function()
+// {
+// var p = this.getSelectedNode();
+// var a = [];
+// if (!p) {
+// a.push(p); // push blank onto stack..
+// p = this.getParentElement();
+// }
+//
+//
+// while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
+// a.push(p);
+// p = p.parentNode;
+// }
+// a.push(this.doc.body);
+// return a;
+// },
+// lastSel : false,
+// lastSelNode : false,
+//
+//
+// getSelection : function()
+// {
+// this.assignDocWin();
+// return Roo.isIE ? this.doc.selection : this.win.getSelection();
+// },
+//
+// getSelectedNode: function()
+// {
+// // this may only work on Gecko!!!
+//
+// // should we cache this!!!!
+//
+//
+//
+//
+// var range = this.createRange(this.getSelection()).cloneRange();
+//
+// if (Roo.isIE) {
+// var parent = range.parentElement();
+// while (true) {
+// var testRange = range.duplicate();
+// testRange.moveToElementText(parent);
+// if (testRange.inRange(range)) {
+// break;
+// }
+// if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
+// break;
+// }
+// parent = parent.parentElement;
+// }
+// return parent;
+// }
+//
+// // is ancestor a text element.
+// var ac = range.commonAncestorContainer;
+// if (ac.nodeType == 3) {
+// ac = ac.parentNode;
+// }
+//
+// var ar = ac.childNodes;
+//
+// var nodes = [];
+// var other_nodes = [];
+// var has_other_nodes = false;
+// for (var i=0;i<ar.length;i++) {
+// if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
+// continue;
+// }
+// // fullly contained node.
+//
+// if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
+// nodes.push(ar[i]);
+// continue;
+// }
+//
+// // probably selected..
+// if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
+// other_nodes.push(ar[i]);
+// continue;
+// }
+// // outer..
+// if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
+// continue;
+// }
+//
+//
+// has_other_nodes = true;
+// }
+// if (!nodes.length && other_nodes.length) {
+// nodes= other_nodes;
+// }
+// if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
+// return false;
+// }
+//
+// return nodes[0];
+// },
+// createRange: function(sel)
+// {
+// // this has strange effects when using with
+// // top toolbar - not sure if it's a great idea.
+// //this.editor.contentWindow.focus();
+// if (typeof sel != "undefined") {
+// try {
+// return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
+// } catch(e) {
+// return this.doc.createRange();
+// }
+// } else {
+// return this.doc.createRange();
+// }
+// },
+// getParentElement: function()
+// {
+//
+// this.assignDocWin();
+// var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
+//
+// var range = this.createRange(sel);
+//
+// try {
+// var p = range.commonAncestorContainer;
+// while (p.nodeType == 3) { // text node
+// p = p.parentNode;
+// }
+// return p;
+// } catch (e) {
+// return null;
+// }
+//
+// },
+// /***
+// *
+// * Range intersection.. the hard stuff...
+// * '-1' = before
+// * '0' = hits..
+// * '1' = after.
+// * [ -- selected range --- ]
+// * [fail] [fail]
+// *
+// * basically..
+// * if end is before start or hits it. fail.
+// * if start is after end or hits it fail.
+// *
+// * if either hits (but other is outside. - then it's not
+// *
+// *
+// **/
+//
+//
+// // @see http://www.thismuchiknow.co.uk/?p=64.
+// rangeIntersectsNode : function(range, node)
+// {
+// var nodeRange = node.ownerDocument.createRange();
+// try {
+// nodeRange.selectNode(node);
+// } catch (e) {
+// nodeRange.selectNodeContents(node);
+// }
+//
+// var rangeStartRange = range.cloneRange();
+// rangeStartRange.collapse(true);
+//
+// var rangeEndRange = range.cloneRange();
+// rangeEndRange.collapse(false);
+//
+// var nodeStartRange = nodeRange.cloneRange();
+// nodeStartRange.collapse(true);
+//
+// var nodeEndRange = nodeRange.cloneRange();
+// nodeEndRange.collapse(false);
+//
+// return rangeStartRange.compareBoundaryPoints(
+// Range.START_TO_START, nodeEndRange) == -1 &&
+// rangeEndRange.compareBoundaryPoints(
+// Range.START_TO_START, nodeStartRange) == 1;
+//
+//
+// },
+// rangeCompareNode : function(range, node)
+// {
+// var nodeRange = node.ownerDocument.createRange();
+// try {
+// nodeRange.selectNode(node);
+// } catch (e) {
+// nodeRange.selectNodeContents(node);
+// }
+//
+//
+// range.collapse(true);
+//
+// nodeRange.collapse(true);
+//
+// var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
+// var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
+//
+// //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
+//
+// var nodeIsBefore = ss == 1;
+// var nodeIsAfter = ee == -1;
+//
+// if (nodeIsBefore && nodeIsAfter)
+// return 0; // outer
+// if (!nodeIsBefore && nodeIsAfter)
+// return 1; //right trailed.
+//
+// if (nodeIsBefore && !nodeIsAfter)
+// return 2; // left trailed.
+// // fully contined.
+// return 3;
+// },
+//
+// // private? - in a new class?
+// cleanUpPaste : function()
+// {
+// // cleans up the whole document..
+// Roo.log('cleanuppaste');
+// this.cleanUpChildren(this.doc.body);
+// var clean = this.cleanWordChars(this.doc.body.innerHTML);
+// if (clean != this.doc.body.innerHTML) {
+// this.doc.body.innerHTML = clean;
+// }
+//
+// },
+//
+// cleanWordChars : function(input) {// change the chars to hex code
+// var he = Roo.bootstrap.HtmlEditor;
+//
+// var output = input;
+// Roo.each(he.swapCodes, function(sw) {
+// var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
+//
+// output = output.replace(swapper, sw[1]);
+// });
+//
+// return output;
+// },
+//
+//
+// cleanUpChildren : function (n)
+// {
+// if (!n.childNodes.length) {
+// return;
+// }
+// for (var i = n.childNodes.length-1; i > -1 ; i--) {
+// this.cleanUpChild(n.childNodes[i]);
+// }
+// },
+//
+//
+//
+//
+// cleanUpChild : function (node)
+// {
+// var ed = this;
+// //console.log(node);
+// if (node.nodeName == "#text") {
+// // clean up silly Windows -- stuff?
+// return;
+// }
+// if (node.nodeName == "#comment") {
+// node.parentNode.removeChild(node);
+// // clean up silly Windows -- stuff?
+// return;
+// }
+//
+// if (Roo.bootstrap.HtmlEditor.black.indexOf(node.tagName.toLowerCase()) > -1) {
+// // remove node.
+// node.parentNode.removeChild(node);
+// return;
+//
+// }
+//
+// var remove_keep_children= Roo.bootstrap.HtmlEditor.remove.indexOf(node.tagName.toLowerCase()) > -1;
+//
+// // remove <a name=....> as rendering on yahoo mailer is borked with this.
+// // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
+//
+// //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
+// // remove_keep_children = true;
+// //}
+//
+// if (remove_keep_children) {
+// this.cleanUpChildren(node);
+// // inserts everything just before this node...
+// while (node.childNodes.length) {
+// var cn = node.childNodes[0];
+// node.removeChild(cn);
+// node.parentNode.insertBefore(cn, node);
+// }
+// node.parentNode.removeChild(node);
+// return;
+// }
+//
+// if (!node.attributes || !node.attributes.length) {
+// this.cleanUpChildren(node);
+// return;
+// }
+//
+// function cleanAttr(n,v)
+// {
+//
+// if (v.match(/^\./) || v.match(/^\//)) {
+// return;
+// }
+// if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/)) {
+// return;
+// }
+// if (v.match(/^#/)) {
+// return;
+// }
+//// Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
+// node.removeAttribute(n);
+//
+// }
+//
+// function cleanStyle(n,v)
+// {
+// if (v.match(/expression/)) { //XSS?? should we even bother..
+// node.removeAttribute(n);
+// return;
+// }
+// var cwhite = typeof(ed.cwhite) == 'undefined' ? Roo.bootstrap.HtmlEditor.cwhite : ed.cwhite;
+// var cblack = typeof(ed.cblack) == 'undefined' ? Roo.bootstrap.HtmlEditor.cblack : ed.cblack;
+//
+//
+// var parts = v.split(/;/);
+// var clean = [];
+//
+// Roo.each(parts, function(p) {
+// p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
+// if (!p.length) {
+// return true;
+// }
+// var l = p.split(':').shift().replace(/\s+/g,'');
+// l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
+//
+//
+// if ( cblack.indexOf(l) > -1) {
+//// Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
+// //node.removeAttribute(n);
+// return true;
+// }
+// //Roo.log()
+// // only allow 'c whitelisted system attributes'
+// if ( cwhite.length && cwhite.indexOf(l) < 0) {
+//// Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
+// //node.removeAttribute(n);
+// return true;
+// }
+//
+//
+//
+//
+// clean.push(p);
+// return true;
+// });
+// if (clean.length) {
+// node.setAttribute(n, clean.join(';'));
+// } else {
+// node.removeAttribute(n);
+// }
+//
+// }
+//
+//
+// for (var i = node.attributes.length-1; i > -1 ; i--) {
+// var a = node.attributes[i];
+// //console.log(a);
+//
+// if (a.name.toLowerCase().substr(0,2)=='on') {
+// node.removeAttribute(a.name);
+// continue;
+// }
+// if (Roo.bootstrap.HtmlEditor.ablack.indexOf(a.name.toLowerCase()) > -1) {
+// node.removeAttribute(a.name);
+// continue;
+// }
+// if (Roo.bootstrap.HtmlEditor.aclean.indexOf(a.name.toLowerCase()) > -1) {
+// cleanAttr(a.name,a.value); // fixme..
+// continue;
+// }
+// if (a.name == 'style') {
+// cleanStyle(a.name,a.value);
+// continue;
+// }
+// /// clean up MS crap..
+// // tecnically this should be a list of valid class'es..
+//
+//
+// if (a.name == 'class') {
+// if (a.value.match(/^Mso/)) {
+// node.className = '';
+// }
+//
+// if (a.value.match(/body/)) {
+// node.className = '';
+// }
+// continue;
+// }
+//
+// // style cleanup!?
+// // class cleanup?
+//
+// }
+//
+//
+// this.cleanUpChildren(node);
+//
+//
+// }
+//
+//
+// // hide stuff that is not compatible
+// /**
+// * @event blur
+// * @hide
+// */
+// /**
+// * @event change
+// * @hide
+// */
+// /**
+// * @event focus
+// * @hide
+// */
+// /**
+// * @event specialkey
+// * @hide
+// */
+// /**
+// * @cfg {String} fieldClass @hide
+// */
+// /**
+// * @cfg {String} focusClass @hide
+// */
+// /**
+// * @cfg {String} autoCreate @hide
+// */
+// /**
+// * @cfg {String} inputType @hide
+// */
+// /**
+// * @cfg {String} invalidClass @hide
+// */
+// /**
+// * @cfg {String} invalidText @hide
+// */
+// /**
+// * @cfg {String} msgFx @hide
+// */
+// /**
+// * @cfg {String} validateOnBlur @hide
+// */
+});
+
+Roo.bootstrap.HtmlEditor.white = [
+ 'area', 'br', 'img', 'input', 'hr', 'wbr',
+
+ 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
+ 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
+ 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
+ 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
+ 'table', 'ul', 'xmp',
+
+ 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
+ 'thead', 'tr',
+
+ 'dir', 'menu', 'ol', 'ul', 'dl',
+
+ 'embed', 'object'
+];
+
+
+Roo.bootstrap.HtmlEditor.black = [
+ // 'embed', 'object', // enable - backend responsiblity to clean thiese
+ 'applet', //
+ 'base', 'basefont', 'bgsound', 'blink', 'body',
+ 'frame', 'frameset', 'head', 'html', 'ilayer',
+ 'iframe', 'layer', 'link', 'meta', 'object',
+ 'script', 'style' ,'title', 'xml' // clean later..
+];
+Roo.bootstrap.HtmlEditor.clean = [
+ 'script', 'style', 'title', 'xml'
+];
+Roo.bootstrap.HtmlEditor.remove = [
+ 'font'
+];
+// attributes..
+
+Roo.bootstrap.HtmlEditor.ablack = [
+ 'on'
+];
+
+Roo.bootstrap.HtmlEditor.aclean = [
+ 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
+];
+
+// protocols..
+Roo.bootstrap.HtmlEditor.pwhite= [
+ 'http', 'https', 'mailto'
+];
+
+// white listed style attributes.
+Roo.bootstrap.HtmlEditor.cwhite= [
+ // 'text-align', /// default is to allow most things..
+
+
+// 'font-size'//??
+];
+
+// black listed style attributes.
+Roo.bootstrap.HtmlEditor.cblack= [
+ // 'font-size' -- this can be set by the project
+];
+
+
+Roo.bootstrap.HtmlEditor.swapCodes =[
+ [ 8211, "--" ],
+ [ 8212, "--" ],
+ [ 8216, "'" ],
+ [ 8217, "'" ],
+ [ 8220, '"' ],
+ [ 8221, '"' ],
+ [ 8226, "*" ],
+ [ 8230, "..." ]
+];
+
+
\ No newline at end of file