+ insertAtCursor : function(text)
+ {
+
+ if(!this.activated){
+ 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) {
+
+ // delete the existing?
+
+ this.createRange(this.getSelection()).deleteContents();
+ range = win.getSelection().getRangeAt(0);
+ node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
+ range.insertNode(node);
+ range = range.cloneRange();
+ range.collapse(false);
+
+ win.getSelection().removeAllRanges();
+ win.getSelection().addRange(range);
+
+
+
+ } 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.relayCmd(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;
+ }
+ /// this is handled by Roo.htmleditor.KeyEnter
+ /*
+ 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;
+ }
+ this.mozKeyPress(e);
+
+ //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.lib.Selection.wrap(Roo.isIE ? this.doc.selection : this.win.getSelection(), this.doc);
+ },
+ /**
+ * Select a dom node
+ * @param {DomElement} node the node to select
+ */
+ selectNode : function(node, collapse)
+ {
+ var nodeRange = node.ownerDocument.createRange();
+ try {
+ nodeRange.selectNode(node);
+ } catch (e) {
+ nodeRange.selectNodeContents(node);
+ }
+ if (collapse === true) {
+ nodeRange.collapse(true);
+ }
+ //
+ var s = this.win.getSelection();
+ s.removeAllRanges();
+ s.addRange(nodeRange);
+ },
+
+ 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;
+ },
+
+ cleanWordChars : function(input) {// change the chars to hex code
+
+ var swapCodes = [
+ [ 8211, "–" ],
+ [ 8212, "—" ],
+ [ 8216, "'" ],
+ [ 8217, "'" ],
+ [ 8220, '"' ],
+ [ 8221, '"' ],
+ [ 8226, "*" ],
+ [ 8230, "..." ]
+ ];
+ var output = input;
+ Roo.each(swapCodes, function(sw) {
+ var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
+
+ output = output.replace(swapper, sw[1]);
+ });
+
+ return output;
+ },
+
+
+
+
+
+ cleanUpChild : function (node)
+ {
+
+ new Roo.htmleditor.FilterComment({node : node});
+ new Roo.htmleditor.FilterAttributes({
+ node : node,
+ attrib_black : this.ablack,
+ attrib_clean : this.aclean,
+ style_white : this.cwhite,
+ style_black : this.cblack
+ });
+ new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
+ new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
+
+
+ },
+
+ /**
+ * Clean up MS wordisms...
+ * @deprecated - use filter directly
+ */
+ cleanWord : function(node)
+ {
+ new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
+ new Roo.htmleditor.FilterKeepChildren({node : node ? node : this.doc.body, tag : [ 'FONT', ':' ]} );
+
+ },
+
+
+ /**
+
+ * @deprecated - use filters
+ */
+ cleanTableWidths : function(node)
+ {
+ new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
+
+
+ },
+
+
+
+ applyBlacklists : function()
+ {
+ var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
+ var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
+
+ this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean : Roo.HtmlEditorCore.aclean;
+ this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack : Roo.HtmlEditorCore.ablack;
+ this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove : Roo.HtmlEditorCore.tag_remove;
+
+ this.white = [];
+ this.black = [];
+ Roo.each(Roo.HtmlEditorCore.white, function(tag) {
+ if (b.indexOf(tag) > -1) {
+ return;
+ }
+ this.white.push(tag);
+
+ }, this);
+
+ Roo.each(w, function(tag) {
+ if (b.indexOf(tag) > -1) {
+ return;
+ }
+ if (this.white.indexOf(tag) > -1) {
+ return;
+ }
+ this.white.push(tag);
+
+ }, this);
+
+
+ Roo.each(Roo.HtmlEditorCore.black, function(tag) {
+ if (w.indexOf(tag) > -1) {
+ return;
+ }
+ this.black.push(tag);
+
+ }, this);
+
+ Roo.each(b, function(tag) {
+ if (w.indexOf(tag) > -1) {
+ return;
+ }
+ if (this.black.indexOf(tag) > -1) {
+ return;
+ }
+ this.black.push(tag);
+
+ }, this);
+
+
+ w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
+ b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
+
+ this.cwhite = [];
+ this.cblack = [];
+ Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
+ if (b.indexOf(tag) > -1) {
+ return;
+ }
+ this.cwhite.push(tag);
+
+ }, this);
+
+ Roo.each(w, function(tag) {
+ if (b.indexOf(tag) > -1) {
+ return;
+ }
+ if (this.cwhite.indexOf(tag) > -1) {
+ return;
+ }
+ this.cwhite.push(tag);
+
+ }, this);
+
+
+ Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
+ if (w.indexOf(tag) > -1) {
+ return;
+ }
+ this.cblack.push(tag);
+
+ }, this);
+
+ Roo.each(b, function(tag) {
+ if (w.indexOf(tag) > -1) {
+ return;
+ }
+ if (this.cblack.indexOf(tag) > -1) {
+ return;
+ }
+ this.cblack.push(tag);
+
+ }, this);
+ },
+
+ setStylesheets : function(stylesheets)
+ {
+ if(typeof(stylesheets) == 'string'){
+ Roo.get(this.iframe.contentDocument.head).createChild({
+ tag : 'link',
+ rel : 'stylesheet',
+ type : 'text/css',
+ href : stylesheets
+ });
+
+ return;
+ }
+ var _this = this;
+
+ Roo.each(stylesheets, function(s) {
+ if(!s.length){
+ return;
+ }
+
+ Roo.get(_this.iframe.contentDocument.head).createChild({
+ tag : 'link',
+ rel : 'stylesheet',
+ type : 'text/css',
+ href : s
+ });
+ });
+
+
+ },
+
+
+ updateLanguage : function()
+ {
+ if (!this.iframe || !this.iframe.contentDocument) {
+ return;
+ }
+ Roo.get(this.iframe.contentDocument.body).attr("lang", this.language);
+ },
+
+
+ removeStylesheets : function()
+ {
+ var _this = this;
+
+ Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
+ s.remove();
+ });
+ },
+
+ setStyle : function(style)
+ {
+ Roo.get(this.iframe.contentDocument.head).createChild({
+ tag : 'style',
+ type : 'text/css',
+ html : style
+ });
+
+ return;
+ }
+
+ // 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.HtmlEditorCore.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.HtmlEditorCore.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',
+ //'FONT' // CLEAN LATER..
+ 'COLGROUP', 'COL' // messy tables.
+
+
+];
+Roo.HtmlEditorCore.clean = [ // ?? needed???
+ 'SCRIPT', 'STYLE', 'TITLE', 'XML'
+];
+Roo.HtmlEditorCore.tag_remove = [
+ 'FONT', 'TBODY'
+];
+// attributes..
+
+Roo.HtmlEditorCore.ablack = [
+ 'on'
+];
+
+Roo.HtmlEditorCore.aclean = [
+ 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
+];
+
+// protocols..
+Roo.HtmlEditorCore.pwhite= [
+ 'http', 'https', 'mailto'
+];
+
+// white listed style attributes.
+Roo.HtmlEditorCore.cwhite= [
+ // 'text-align', /// default is to allow most things..
+
+
+// 'font-size'//??
+];
+
+// black listed style attributes.
+Roo.HtmlEditorCore.cblack= [
+ // 'font-size' -- this can be set by the project
+];
+
+
+
+
+ //<script type="text/javascript">
+
+/*
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ * Licence LGPL
+ *
+ */
+
+
+Roo.form.HtmlEditor = function(config){
+
+
+
+ Roo.form.HtmlEditor.superclass.constructor.call(this, config);
+
+ if (!this.toolbars) {
+ this.toolbars = [];
+ }
+ this.editorcore = new Roo.HtmlEditorCore(Roo.apply({ owner : this} , config));
+
+
+};
+
+/**
+ * @class Roo.form.HtmlEditor
+ * @extends Roo.form.Field
+ * Provides a lightweight HTML Editor component.
+ *
+ * This has been tested on Fireforx / Chrome.. IE may not be so great..
+ *
+ * <br><br><b>Note: The focus/blur and validation marking functionality inherited from Ext.form.Field is NOT
+ * supported by this editor.</b><br/><br/>
+ * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within
+ * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
+ */
+Roo.extend(Roo.form.HtmlEditor, Roo.form.Field, {
+ /**
+ * @cfg {Boolean} clearUp
+ */
+ clearUp : true,
+ /**
+ * @cfg {Array} toolbars Array of toolbars. - defaults to just the Standard one
+ */
+ toolbars : false,
+
+ /**
+ * @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 - this is usally a good idea rootURL + '/roojs1/css/undoreset.css', .
+ *
+ */
+ stylesheets: false,
+
+
+ /**
+ * @cfg {Array} blacklist of css styles style attributes (blacklist overrides whitelist)
+ *
+ */
+ cblack: false,
+ /**
+ * @cfg {Array} whitelist of css styles style attributes (blacklist overrides whitelist)
+ *
+ */
+ cwhite: false,
+
+ /**
+ * @cfg {Array} blacklist of html tags - in addition to standard blacklist.
+ *
+ */
+ black: false,
+ /**
+ * @cfg {Array} whitelist of html tags - in addition to statndard whitelist
+ *
+ */
+ white: false,
+ /**
+ * @cfg {boolean} allowComments - default false - allow comments in HTML source - by default they are stripped - if you are editing email you may need this.
+ */
+ allowComments: false,
+ /**
+ * @cfg {boolean} enableBlocks - default true - if the block editor (table and figure should be enabled)
+ */
+ enableBlocks : true,
+
+ /**
+ * @cfg {boolean} autoClean - default true - loading and saving will remove quite a bit of formating,
+ * if you are doing an email editor, this probably needs disabling, it's designed
+ */
+ autoClean: true,
+ /**
+ * @cfg {string} bodyCls default '' default classes to add to body of editable area - usually undoreset is a good start..
+ */
+ bodyCls : '',
+ /**
+ * @cfg {String} language default en - language of text (usefull for rtl languages)
+ *
+ */
+ language: 'en',
+
+
+ // id of frame..
+ frameId: false,
+
+ // private properties
+ validationEvent : false,
+ deferHeight: true,
+ initialized : false,
+ activated : false,
+
+ onFocus : Roo.emptyFn,
+ iframePad:3,
+ hideMode:'offsets',
+
+ actionMode : 'container', // defaults to hiding it...
+
+ defaultAutoCreate : { // modified by initCompnoent..
+ tag: "textarea",
+ style:"width:500px;height:300px;",
+ autocomplete: "new-password"
+ },
+
+ // private
+ initComponent : function(){
+ 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,
+ /**
+ * @event firstfocus
+ * Fires when on first focus - needed by toolbars..
+ * @param {HtmlEditor} this
+ */
+ firstfocus: true,
+ /**
+ * @event autosave
+ * Auto save the htmlEditor value as a file into Events
+ * @param {HtmlEditor} this
+ */
+ autosave: true,
+ /**
+ * @event savedpreview
+ * preview the saved version of htmlEditor
+ * @param {HtmlEditor} this
+ */
+ savedpreview: true,
+
+ /**
+ * @event stylesheetsclick
+ * Fires when press the Sytlesheets button
+ * @param {Roo.HtmlEditorCore} this
+ */
+ stylesheetsclick: true,
+ /**
+ * @event paste
+ * Fires when press user pastes into the editor
+ * @param {Roo.HtmlEditorCore} this
+ */
+ paste: true
+ });
+ this.defaultAutoCreate = {
+ tag: "textarea",
+ style:'width: ' + this.width + 'px;height: ' + this.height + 'px;',
+ autocomplete: "new-password"
+ };
+ },
+
+ /**
+ * 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 {HtmlEditor} editor
+ */
+ createToolbar : function(editor){
+ Roo.log("create toolbars");
+ if (!editor.toolbars || !editor.toolbars.length) {
+ editor.toolbars = [ new Roo.form.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.form.HtmlEditor);
+ editor.toolbars[i].init(editor);
+ }
+
+
+ },
+ /**
+ * get the Context selected node
+ * @returns {DomElement|boolean} selected node if active or false if none
+ *
+ */
+ getSelectedNode : function()
+ {
+ if (this.toolbars.length < 2 || !this.toolbars[1].tb) {
+ return false;
+ }
+ return this.toolbars[1].tb.selectedNode;
+
+ },
+ // private
+ onRender : function(ct, position)
+ {
+ var _t = this;
+ Roo.form.HtmlEditor.superclass.onRender.call(this, ct, position);
+
+ this.wrap = this.el.wrap({
+ cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
+ });
+
+ this.editorcore.onRender(ct, position);
+
+ 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.createToolbar(this);
+
+
+ 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..
+ }
+
+ this.keyNav = new Roo.KeyNav(this.el, {
+
+ "tab" : function(e){
+ e.preventDefault();
+
+ var value = this.getValue();
+
+ var start = this.el.dom.selectionStart;
+ var end = this.el.dom.selectionEnd;
+
+ if(!e.shiftKey){
+
+ this.setValue(value.substring(0, start) + "\t" + value.substring(end));
+ this.el.dom.setSelectionRange(end + 1, end + 1);
+ return;
+ }
+
+ var f = value.substring(0, start).split("\t");
+
+ if(f.pop().length != 0){
+ return;
+ }
+
+ this.setValue(f.join("\t") + value.substring(end));
+ this.el.dom.setSelectionRange(start - 1, start - 1);
+
+ },
+
+ "home" : function(e){
+ e.preventDefault();
+
+ var curr = this.el.dom.selectionStart;
+ var lines = this.getValue().split("\n");
+
+ if(!lines.length){
+ return;
+ }
+
+ if(e.ctrlKey){
+ this.el.dom.setSelectionRange(0, 0);
+ return;
+ }
+
+ var pos = 0;
+
+ for (var i = 0; i < lines.length;i++) {
+ pos += lines[i].length;
+
+ if(i != 0){
+ pos += 1;
+ }
+
+ if(pos < curr){
+ continue;
+ }
+
+ pos -= lines[i].length;
+
+ break;
+ }
+
+ if(!e.shiftKey){
+ this.el.dom.setSelectionRange(pos, pos);
+ return;
+ }
+
+ this.el.dom.selectionStart = pos;
+ this.el.dom.selectionEnd = curr;
+ },
+
+ "end" : function(e){
+ e.preventDefault();
+
+ var curr = this.el.dom.selectionStart;
+ var lines = this.getValue().split("\n");
+
+ if(!lines.length){
+ return;
+ }
+
+ if(e.ctrlKey){
+ this.el.dom.setSelectionRange(this.getValue().length, this.getValue().length);
+ return;
+ }
+
+ var pos = 0;
+
+ for (var i = 0; i < lines.length;i++) {
+
+ pos += lines[i].length;
+
+ if(i != 0){
+ pos += 1;
+ }
+
+ if(pos < curr){
+ continue;
+ }
+
+ break;
+ }
+
+ if(!e.shiftKey){
+ this.el.dom.setSelectionRange(pos, pos);
+ return;
+ }
+
+ this.el.dom.selectionStart = curr;
+ this.el.dom.selectionEnd = pos;
+ },
+
+ scope : this,
+
+ doRelay : function(foo, bar, hname){
+ return Roo.KeyNav.prototype.doRelay.apply(this, arguments);
+ },
+
+ forceKeyDown: true
+ });
+
+// if(this.autosave && this.w){
+// this.autoSaveFn = setInterval(this.autosave, 1000);
+// }
+ },
+
+ // private
+ onResize : function(w, h)
+ {
+ Roo.form.HtmlEditor.superclass.onResize.apply(this, arguments);
+ var ew = false;
+ var eh = false;
+
+ if(this.el ){
+ if(typeof w == 'number'){
+ var aw = w - this.wrap.getFrameWidth('lr');
+ this.el.setWidth(this.adjustWidth('textarea', aw));
+ ew = aw;
+ }
+ 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..
+// Roo.log(ah);
+ this.el.setHeight(this.adjustWidth('textarea', ah));
+ var eh = ah;
+ }
+ }
+ Roo.log('onResize:' + [w,h,ew,eh].join(',') );
+ this.editorcore.onResize(ew,eh);
+
+ },
+
+ /**
+ * Toggles the editor between standard and source edit mode.
+ * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
+ */
+ toggleSourceEdit : function(sourceEditMode)
+ {
+ this.editorcore.toggleSourceEdit(sourceEditMode);
+
+ if(this.editorcore.sourceEditMode){
+ Roo.log('editor - showing textarea');
+
+// Roo.log('in');
+// Roo.log(this.syncValue());
+ this.editorcore.syncValue();
+ this.el.removeClass('x-hidden');
+ this.el.dom.removeAttribute('tabIndex');
+ this.el.focus();
+ this.el.dom.scrollTop = 0;
+
+
+ for (var i = 0; i < this.toolbars.length; i++) {
+ if(this.toolbars[i] instanceof Roo.form.HtmlEditor.ToolbarContext){
+ this.toolbars[i].tb.hide();
+ this.toolbars[i].footer.hide();
+ }
+ }
+
+ }else{
+ Roo.log('editor - hiding textarea');
+// Roo.log('out')
+// Roo.log(this.pushValue());
+ this.editorcore.pushValue();
+
+ this.el.addClass('x-hidden');
+ this.el.dom.setAttribute('tabIndex', -1);
+
+ for (var i = 0; i < this.toolbars.length; i++) {
+ if(this.toolbars[i] instanceof Roo.form.HtmlEditor.ToolbarContext){
+ this.toolbars[i].tb.show();
+ this.toolbars[i].footer.show();
+ }
+ }
+
+ //this.deferFocus();
+ }
+
+ this.setSize(this.wrap.getSize());
+ this.onResize(this.wrap.getSize().width, this.wrap.getSize().height);
+
+ this.fireEvent('editmodechange', this, this.editorcore.sourceEditMode);
+ },
+
+ // 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.form.HtmlEditor.superclass.setValue.call(this, v);
+ this.editorcore.pushValue();
+ },
+
+ /**
+ * update the language in the body - really done by core
+ * @param {String} language - eg. en / ar / zh-CN etc..
+ */
+ updateLanguage : function(lang)
+ {
+ this.language = lang;
+ this.editorcore.language = lang;
+ this.editorcore.updateLanguage();
+
+ },
+ // private
+ deferFocus : function(){
+ this.focus.defer(10, this);
+ },
+
+ // doc'ed in Field
+ focus : function(){
+ this.editorcore.focus();
+
+ },
+
+
+ // 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(){
+ //Roo.log("onFirstFocus");
+ this.editorcore.onFirstFocus();
+ for (var i =0; i < this.toolbars.length;i++) {
+ this.toolbars[i].onFirstFocus();
+ }
+
+ },
+
+ // private
+ syncValue : function()
+ {
+ this.editorcore.syncValue();
+ },
+
+ pushValue : function()
+ {
+ this.editorcore.pushValue();
+ },
+
+ setStylesheets : function(stylesheets)
+ {
+ this.editorcore.setStylesheets(stylesheets);
+ },
+
+ removeStylesheets : function()
+ {
+ this.editorcore.removeStylesheets();
+ }
+
+
+ // hide stuff that is not compatible
+ /**
+ * @event blur
+ * @hide
+ */
+ /**
+ * @event change
+ * @hide
+ */
+ /**
+ * @event focus
+ * @hide
+ */
+ /**
+ * @event specialkey
+ * @hide
+ */
+ /**
+ * @cfg {String} fieldClass @hide