summaryrefslogtreecommitdiff
path: root/lib/dijit/Editor.js
diff options
context:
space:
mode:
authorAndrew Dolgov <[email protected]>2011-11-08 20:40:44 +0400
committerAndrew Dolgov <[email protected]>2011-11-08 20:40:44 +0400
commit81bea17aefb26859f825b9293c7c99192874806e (patch)
treefb244408ca271affa2899adb634788802c9a89d8 /lib/dijit/Editor.js
parent870a70e109ac9e80a88047044530de53d0404ec7 (diff)
upgrade Dojo to 1.6.1
Diffstat (limited to 'lib/dijit/Editor.js')
-rw-r--r--lib/dijit/Editor.js1231
1 files changed, 776 insertions, 455 deletions
diff --git a/lib/dijit/Editor.js b/lib/dijit/Editor.js
index 69258eade..b77dd7165 100644
--- a/lib/dijit/Editor.js
+++ b/lib/dijit/Editor.js
@@ -1,12 +1,12 @@
/*
- Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved.
+ Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
Available via Academic Free License >= 2.1 OR the modified BSD license.
see: http://dojotoolkit.org/license for details
*/
-if(!dojo._hasResource["dijit.Editor"]){
-dojo._hasResource["dijit.Editor"]=true;
+if(!dojo._hasResource["dijit.Editor"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
+dojo._hasResource["dijit.Editor"] = true;
dojo.provide("dijit.Editor");
dojo.require("dijit._editor.RichText");
dojo.require("dijit.Toolbar");
@@ -17,457 +17,778 @@ dojo.require("dijit._editor.range");
dojo.require("dijit._Container");
dojo.require("dojo.i18n");
dojo.require("dijit.layout._LayoutWidget");
-dojo.require("dijit._editor.range");
-dojo.requireLocalization("dijit._editor","commands",null,"ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw");
-dojo.declare("dijit.Editor",dijit._editor.RichText,{plugins:null,extraPlugins:null,constructor:function(){
-if(!dojo.isArray(this.plugins)){
-this.plugins=["undo","redo","|","cut","copy","paste","|","bold","italic","underline","strikethrough","|","insertOrderedList","insertUnorderedList","indent","outdent","|","justifyLeft","justifyRight","justifyCenter","justifyFull","dijit._editor.plugins.EnterKeyHandling"];
-}
-this._plugins=[];
-this._editInterval=this.editActionInterval*1000;
-if(dojo.isIE){
-this.events.push("onBeforeDeactivate");
-this.events.push("onBeforeActivate");
-}
-},postCreate:function(){
-this._steps=this._steps.slice(0);
-this._undoedSteps=this._undoedSteps.slice(0);
-if(dojo.isArray(this.extraPlugins)){
-this.plugins=this.plugins.concat(this.extraPlugins);
-}
-this.setValueDeferred=new dojo.Deferred();
-this.inherited(arguments);
-this.commands=dojo.i18n.getLocalization("dijit._editor","commands",this.lang);
-if(!this.toolbar){
-this.toolbar=new dijit.Toolbar({dir:this.dir,lang:this.lang});
-this.header.appendChild(this.toolbar.domNode);
-}
-dojo.forEach(this.plugins,this.addPlugin,this);
-this.setValueDeferred.callback(true);
-dojo.addClass(this.iframe.parentNode,"dijitEditorIFrameContainer");
-dojo.addClass(this.iframe,"dijitEditorIFrame");
-dojo.attr(this.iframe,"allowTransparency",true);
-if(dojo.isWebKit){
-dojo.style(this.domNode,"KhtmlUserSelect","none");
-}
-this.toolbar.startup();
-this.onNormalizedDisplayChanged();
-},destroy:function(){
-dojo.forEach(this._plugins,function(p){
-if(p&&p.destroy){
-p.destroy();
-}
-});
-this._plugins=[];
-this.toolbar.destroyRecursive();
-delete this.toolbar;
-this.inherited(arguments);
-},addPlugin:function(_1,_2){
-var _3=dojo.isString(_1)?{name:_1}:_1;
-if(!_3.setEditor){
-var o={"args":_3,"plugin":null,"editor":this};
-dojo.publish(dijit._scopeName+".Editor.getPlugin",[o]);
-if(!o.plugin){
-var pc=dojo.getObject(_3.name);
-if(pc){
-o.plugin=new pc(_3);
-}
-}
-if(!o.plugin){
-console.warn("Cannot find plugin",_1);
-return;
-}
-_1=o.plugin;
-}
-if(arguments.length>1){
-this._plugins[_2]=_1;
-}else{
-this._plugins.push(_1);
-}
-_1.setEditor(this);
-if(dojo.isFunction(_1.setToolbar)){
-_1.setToolbar(this.toolbar);
-}
-},startup:function(){
-},resize:function(_4){
-if(_4){
-dijit.layout._LayoutWidget.prototype.resize.apply(this,arguments);
-}
-},layout:function(){
-var _5=(this._contentBox.h-(this.getHeaderHeight()+this.getFooterHeight()+dojo._getPadBorderExtents(this.iframe.parentNode).h+dojo._getMarginExtents(this.iframe.parentNode).h));
-this.editingArea.style.height=_5+"px";
-if(this.iframe){
-this.iframe.style.height="100%";
-}
-this._layoutMode=true;
-},_onIEMouseDown:function(e){
-var _6;
-var b=this.document.body;
-var _7=b.clientWidth;
-var _8=b.clientHeight;
-var _9=b.clientLeft;
-var _a=b.offsetWidth;
-var _b=b.offsetHeight;
-var _c=b.offsetLeft;
-bodyDir=b.dir?b.dir.toLowerCase():"";
-if(bodyDir!="rtl"){
-if(_7<_a&&e.x>_7&&e.x<_a){
-_6=true;
-}
-}else{
-if(e.x<_9&&e.x>_c){
-_6=true;
-}
-}
-if(!_6){
-if(_8<_b&&e.y>_8&&e.y<_b){
-_6=true;
-}
-}
-if(!_6){
-delete this._cursorToStart;
-delete this._savedSelection;
-if(e.target.tagName=="BODY"){
-setTimeout(dojo.hitch(this,"placeCursorAtEnd"),0);
-}
-this.inherited(arguments);
-}
-},onBeforeActivate:function(e){
-this._restoreSelection();
-},onBeforeDeactivate:function(e){
-if(this.customUndo){
-this.endEditing(true);
-}
-if(e.target.tagName!="BODY"){
-this._saveSelection();
-}
-},customUndo:dojo.isIE||dojo.isWebKit,editActionInterval:3,beginEditing:function(_d){
-if(!this._inEditing){
-this._inEditing=true;
-this._beginEditing(_d);
-}
-if(this.editActionInterval>0){
-if(this._editTimer){
-clearTimeout(this._editTimer);
-}
-this._editTimer=setTimeout(dojo.hitch(this,this.endEditing),this._editInterval);
-}
-},_steps:[],_undoedSteps:[],execCommand:function(_e){
-if(this.customUndo&&(_e=="undo"||_e=="redo")){
-return this[_e]();
-}else{
-if(this.customUndo){
-this.endEditing();
-this._beginEditing();
-}
-var r;
-try{
-r=this.inherited("execCommand",arguments);
-if(dojo.isWebKit&&_e=="paste"&&!r){
-throw {code:1011};
-}
-}
-catch(e){
-if(e.code==1011&&/copy|cut|paste/.test(_e)){
-var _f=dojo.string.substitute,_10={cut:"X",copy:"C",paste:"V"};
-alert(_f(this.commands.systemShortcut,[this.commands[_e],_f(this.commands[dojo.isMac?"appleKey":"ctrlKey"],[_10[_e]])]));
-}
-r=false;
-}
-if(this.customUndo){
-this._endEditing();
-}
-return r;
-}
-},queryCommandEnabled:function(cmd){
-if(this.customUndo&&(cmd=="undo"||cmd=="redo")){
-return cmd=="undo"?(this._steps.length>1):(this._undoedSteps.length>0);
-}else{
-return this.inherited("queryCommandEnabled",arguments);
-}
-},_moveToBookmark:function(b){
-var _11=b.mark;
-var _12=b.mark;
-var col=b.isCollapsed;
-var r,_13,_14,sel;
-if(_12){
-if(dojo.isIE){
-if(dojo.isArray(_12)){
-_11=[];
-dojo.forEach(_12,function(n){
-_11.push(dijit.range.getNode(n,this.editNode));
-},this);
-dojo.withGlobal(this.window,"moveToBookmark",dijit,[{mark:_11,isCollapsed:col}]);
-}else{
-if(_12.startContainer&&_12.endContainer){
-sel=dijit.range.getSelection(this.window);
-if(sel&&sel.removeAllRanges){
-sel.removeAllRanges();
-r=dijit.range.create(this.window);
-_13=dijit.range.getNode(_12.startContainer,this.editNode);
-_14=dijit.range.getNode(_12.endContainer,this.editNode);
-if(_13&&_14){
-r.setStart(_13,_12.startOffset);
-r.setEnd(_14,_12.endOffset);
-sel.addRange(r);
-}
-}
-}
-}
-}else{
-sel=dijit.range.getSelection(this.window);
-if(sel&&sel.removeAllRanges){
-sel.removeAllRanges();
-r=dijit.range.create(this.window);
-_13=dijit.range.getNode(_12.startContainer,this.editNode);
-_14=dijit.range.getNode(_12.endContainer,this.editNode);
-if(_13&&_14){
-r.setStart(_13,_12.startOffset);
-r.setEnd(_14,_12.endOffset);
-sel.addRange(r);
-}
-}
-}
-}
-},_changeToStep:function(_15,to){
-this.setValue(to.text);
-var b=to.bookmark;
-if(!b){
-return;
-}
-this._moveToBookmark(b);
-},undo:function(){
-var ret=false;
-if(!this._undoRedoActive){
-this._undoRedoActive=true;
-this.endEditing(true);
-var s=this._steps.pop();
-if(s&&this._steps.length>0){
-this.focus();
-this._changeToStep(s,this._steps[this._steps.length-1]);
-this._undoedSteps.push(s);
-this.onDisplayChanged();
-delete this._undoRedoActive;
-ret=true;
-}
-delete this._undoRedoActive;
-}
-return ret;
-},redo:function(){
-var ret=false;
-if(!this._undoRedoActive){
-this._undoRedoActive=true;
-this.endEditing(true);
-var s=this._undoedSteps.pop();
-if(s&&this._steps.length>0){
-this.focus();
-this._changeToStep(this._steps[this._steps.length-1],s);
-this._steps.push(s);
-this.onDisplayChanged();
-ret=true;
-}
-delete this._undoRedoActive;
-}
-return ret;
-},endEditing:function(_16){
-if(this._editTimer){
-clearTimeout(this._editTimer);
-}
-if(this._inEditing){
-this._endEditing(_16);
-this._inEditing=false;
-}
-},_getBookmark:function(){
-var b=dojo.withGlobal(this.window,dijit.getBookmark);
-var tmp=[];
-if(b&&b.mark){
-var _17=b.mark;
-if(dojo.isIE){
-var sel=dijit.range.getSelection(this.window);
-if(!dojo.isArray(_17)){
-if(sel){
-var _18;
-if(sel.rangeCount){
-_18=sel.getRangeAt(0);
-}
-if(_18){
-b.mark=_18.cloneRange();
-}else{
-b.mark=dojo.withGlobal(this.window,dijit.getBookmark);
-}
-}
-}else{
-dojo.forEach(b.mark,function(n){
-tmp.push(dijit.range.getIndex(n,this.editNode).o);
-},this);
-b.mark=tmp;
-}
-}
-try{
-if(b.mark&&b.mark.startContainer){
-tmp=dijit.range.getIndex(b.mark.startContainer,this.editNode).o;
-b.mark={startContainer:tmp,startOffset:b.mark.startOffset,endContainer:b.mark.endContainer===b.mark.startContainer?tmp:dijit.range.getIndex(b.mark.endContainer,this.editNode).o,endOffset:b.mark.endOffset};
-}
-}
-catch(e){
-b.mark=null;
-}
-}
-return b;
-},_beginEditing:function(cmd){
-if(this._steps.length===0){
-this._steps.push({"text":dijit._editor.getChildrenHtml(this.editNode),"bookmark":this._getBookmark()});
-}
-},_endEditing:function(_19){
-var v=dijit._editor.getChildrenHtml(this.editNode);
-this._undoedSteps=[];
-this._steps.push({text:v,bookmark:this._getBookmark()});
-},onKeyDown:function(e){
-if(!dojo.isIE&&!this.iframe&&e.keyCode==dojo.keys.TAB&&!this.tabIndent){
-this._saveSelection();
-}
-if(!this.customUndo){
-this.inherited(arguments);
-return;
-}
-var k=e.keyCode,ks=dojo.keys;
-if(e.ctrlKey&&!e.altKey){
-if(k==90||k==122){
-dojo.stopEvent(e);
-this.undo();
-return;
-}else{
-if(k==89||k==121){
-dojo.stopEvent(e);
-this.redo();
-return;
-}
-}
-}
-this.inherited(arguments);
-switch(k){
-case ks.ENTER:
-case ks.BACKSPACE:
-case ks.DELETE:
-this.beginEditing();
-break;
-case 88:
-case 86:
-if(e.ctrlKey&&!e.altKey&&!e.metaKey){
-this.endEditing();
-if(e.keyCode==88){
-this.beginEditing("cut");
-setTimeout(dojo.hitch(this,this.endEditing),1);
-}else{
-this.beginEditing("paste");
-setTimeout(dojo.hitch(this,this.endEditing),1);
-}
-break;
-}
-default:
-if(!e.ctrlKey&&!e.altKey&&!e.metaKey&&(e.keyCode<dojo.keys.F1||e.keyCode>dojo.keys.F15)){
-this.beginEditing();
-break;
-}
-case ks.ALT:
-this.endEditing();
-break;
-case ks.UP_ARROW:
-case ks.DOWN_ARROW:
-case ks.LEFT_ARROW:
-case ks.RIGHT_ARROW:
-case ks.HOME:
-case ks.END:
-case ks.PAGE_UP:
-case ks.PAGE_DOWN:
-this.endEditing(true);
-break;
-case ks.CTRL:
-case ks.SHIFT:
-case ks.TAB:
-break;
-}
-},_onBlur:function(){
-this.inherited("_onBlur",arguments);
-this.endEditing(true);
-},_saveSelection:function(){
-this._savedSelection=this._getBookmark();
-},_restoreSelection:function(){
-if(this._savedSelection){
-delete this._cursorToStart;
-if(dojo.withGlobal(this.window,"isCollapsed",dijit)){
-this._moveToBookmark(this._savedSelection);
-}
-delete this._savedSelection;
-}
-},onClick:function(){
-this.endEditing(true);
-this.inherited(arguments);
-},_setDisabledAttr:function(_1a){
-if(!this.disabled&&_1a){
-this._buttonEnabledPlugins=dojo.filter(this._plugins,function(p){
-if(p&&p.button&&!p.button.get("disabled")){
-p.button.set("disabled",true);
-return true;
-}
-return false;
-});
-}else{
-if(this.disabled&&!_1a){
-dojo.forEach(this._buttonEnabledPlugins,function(p){
-p.button.attr("disabled",false);
-p.updateState&&p.updateState();
-});
-}
-}
-this.inherited(arguments);
-},_setStateClass:function(){
-this.inherited(arguments);
-if(this.document&&this.document.body){
-dojo.style(this.document.body,"color",dojo.style(this.iframe,"color"));
-}
-}});
-dojo.subscribe(dijit._scopeName+".Editor.getPlugin",null,function(o){
-if(o.plugin){
-return;
-}
-var _1b=o.args,p;
-var _1c=dijit._editor._Plugin;
-var _1d=_1b.name;
-switch(_1d){
-case "undo":
-case "redo":
-case "cut":
-case "copy":
-case "paste":
-case "insertOrderedList":
-case "insertUnorderedList":
-case "indent":
-case "outdent":
-case "justifyCenter":
-case "justifyFull":
-case "justifyLeft":
-case "justifyRight":
-case "delete":
-case "selectAll":
-case "removeFormat":
-case "unlink":
-case "insertHorizontalRule":
-p=new _1c({command:_1d});
-break;
-case "bold":
-case "italic":
-case "underline":
-case "strikethrough":
-case "subscript":
-case "superscript":
-p=new _1c({buttonClass:dijit.form.ToggleButton,command:_1d});
-break;
-case "|":
-p=new _1c({button:new dijit.ToolbarSeparator(),setEditor:function(_1e){
-this.editor=_1e;
-}});
-}
-o.plugin=p;
+dojo.requireLocalization("dijit._editor", "commands", null, "ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw");
+
+
+dojo.declare(
+ "dijit.Editor",
+ dijit._editor.RichText,
+ {
+ // summary:
+ // A rich text Editing widget
+ //
+ // description:
+ // This widget provides basic WYSIWYG editing features, based on the browser's
+ // underlying rich text editing capability, accompanied by a toolbar (`dijit.Toolbar`).
+ // A plugin model is available to extend the editor's capabilities as well as the
+ // the options available in the toolbar. Content generation may vary across
+ // browsers, and clipboard operations may have different results, to name
+ // a few limitations. Note: this widget should not be used with the HTML
+ // &lt;TEXTAREA&gt; tag -- see dijit._editor.RichText for details.
+
+ // plugins: [const] Object[]
+ // A list of plugin names (as strings) or instances (as objects)
+ // for this widget.
+ //
+ // When declared in markup, it might look like:
+ // | plugins="['bold',{name:'dijit._editor.plugins.FontChoice', command:'fontName', generic:true}]"
+ plugins: null,
+
+ // extraPlugins: [const] Object[]
+ // A list of extra plugin names which will be appended to plugins array
+ extraPlugins: null,
+
+ constructor: function(){
+ // summary:
+ // Runs on widget initialization to setup arrays etc.
+ // tags:
+ // private
+
+ if(!dojo.isArray(this.plugins)){
+ this.plugins=["undo","redo","|","cut","copy","paste","|","bold","italic","underline","strikethrough","|",
+ "insertOrderedList","insertUnorderedList","indent","outdent","|","justifyLeft","justifyRight","justifyCenter","justifyFull",
+ "dijit._editor.plugins.EnterKeyHandling" /*, "createLink"*/];
+ }
+
+ this._plugins=[];
+ this._editInterval = this.editActionInterval * 1000;
+
+ //IE will always lose focus when other element gets focus, while for FF and safari,
+ //when no iframe is used, focus will be lost whenever another element gets focus.
+ //For IE, we can connect to onBeforeDeactivate, which will be called right before
+ //the focus is lost, so we can obtain the selected range. For other browsers,
+ //no equivelent of onBeforeDeactivate, so we need to do two things to make sure
+ //selection is properly saved before focus is lost: 1) when user clicks another
+ //element in the page, in which case we listen to mousedown on the entire page and
+ //see whether user clicks out of a focus editor, if so, save selection (focus will
+ //only lost after onmousedown event is fired, so we can obtain correct caret pos.)
+ //2) when user tabs away from the editor, which is handled in onKeyDown below.
+ if(dojo.isIE){
+ this.events.push("onBeforeDeactivate");
+ this.events.push("onBeforeActivate");
+ }
+ },
+
+ postMixInProperties: function() {
+ // summary:
+ // Extension to make sure a deferred is in place before certain functions
+ // execute, like making sure all the plugins are properly inserted.
+
+ // Set up a deferred so that the value isn't applied to the editor
+ // until all the plugins load, needed to avoid timing condition
+ // reported in #10537.
+ this.setValueDeferred = new dojo.Deferred();
+ this.inherited(arguments);
+ },
+
+ postCreate: function(){
+ //for custom undo/redo, if enabled.
+ this._steps=this._steps.slice(0);
+ this._undoedSteps=this._undoedSteps.slice(0);
+
+ if(dojo.isArray(this.extraPlugins)){
+ this.plugins=this.plugins.concat(this.extraPlugins);
+ }
+
+ this.inherited(arguments);
+
+ this.commands = dojo.i18n.getLocalization("dijit._editor", "commands", this.lang);
+
+ if(!this.toolbar){
+ // if we haven't been assigned a toolbar, create one
+ this.toolbar = new dijit.Toolbar({
+ dir: this.dir,
+ lang: this.lang
+ });
+ this.header.appendChild(this.toolbar.domNode);
+ }
+
+ dojo.forEach(this.plugins, this.addPlugin, this);
+
+ // Okay, denote the value can now be set.
+ this.setValueDeferred.callback(true);
+
+ dojo.addClass(this.iframe.parentNode, "dijitEditorIFrameContainer");
+ dojo.addClass(this.iframe, "dijitEditorIFrame");
+ dojo.attr(this.iframe, "allowTransparency", true);
+
+ if(dojo.isWebKit){
+ // Disable selecting the entire editor by inadvertant double-clicks.
+ // on buttons, title bar, etc. Otherwise clicking too fast on
+ // a button such as undo/redo selects the entire editor.
+ dojo.style(this.domNode, "KhtmlUserSelect", "none");
+ }
+ this.toolbar.startup();
+ this.onNormalizedDisplayChanged(); //update toolbar button status
+ },
+ destroy: function(){
+ dojo.forEach(this._plugins, function(p){
+ if(p && p.destroy){
+ p.destroy();
+ }
+ });
+ this._plugins=[];
+ this.toolbar.destroyRecursive();
+ delete this.toolbar;
+ this.inherited(arguments);
+ },
+ addPlugin: function(/*String||Object*/plugin, /*Integer?*/index){
+ // summary:
+ // takes a plugin name as a string or a plugin instance and
+ // adds it to the toolbar and associates it with this editor
+ // instance. The resulting plugin is added to the Editor's
+ // plugins array. If index is passed, it's placed in the plugins
+ // array at that index. No big magic, but a nice helper for
+ // passing in plugin names via markup.
+ //
+ // plugin: String, args object or plugin instance
+ //
+ // args:
+ // This object will be passed to the plugin constructor
+ //
+ // index: Integer
+ // Used when creating an instance from
+ // something already in this.plugins. Ensures that the new
+ // instance is assigned to this.plugins at that index.
+ var args=dojo.isString(plugin)?{name:plugin}:plugin;
+ if(!args.setEditor){
+ var o={"args":args,"plugin":null,"editor":this};
+ dojo.publish(dijit._scopeName + ".Editor.getPlugin",[o]);
+ if(!o.plugin){
+ var pc = dojo.getObject(args.name);
+ if(pc){
+ o.plugin=new pc(args);
+ }
+ }
+ if(!o.plugin){
+ console.warn('Cannot find plugin',plugin);
+ return;
+ }
+ plugin=o.plugin;
+ }
+ if(arguments.length > 1){
+ this._plugins[index] = plugin;
+ }else{
+ this._plugins.push(plugin);
+ }
+ plugin.setEditor(this);
+ if(dojo.isFunction(plugin.setToolbar)){
+ plugin.setToolbar(this.toolbar);
+ }
+ },
+ //the following 3 functions are required to make the editor play nice under a layout widget, see #4070
+ startup: function(){
+ // summary:
+ // Exists to make Editor work as a child of a layout widget.
+ // Developers don't need to call this method.
+ // tags:
+ // protected
+ //console.log('startup',arguments);
+ },
+ resize: function(size){
+ // summary:
+ // Resize the editor to the specified size, see `dijit.layout._LayoutWidget.resize`
+ if(size){
+ // we've been given a height/width for the entire editor (toolbar + contents), calls layout()
+ // to split the allocated size between the toolbar and the contents
+ dijit.layout._LayoutWidget.prototype.resize.apply(this, arguments);
+ }
+ /*
+ else{
+ // do nothing, the editor is already laid out correctly. The user has probably specified
+ // the height parameter, which was used to set a size on the iframe
+ }
+ */
+ },
+ layout: function(){
+ // summary:
+ // Called from `dijit.layout._LayoutWidget.resize`. This shouldn't be called directly
+ // tags:
+ // protected
+
+ // Converts the iframe (or rather the <div> surrounding it) to take all the available space
+ // except what's needed for the header (toolbars) and footer (breadcrumbs, etc).
+ // A class was added to the iframe container and some themes style it, so we have to
+ // calc off the added margins and padding too. See tracker: #10662
+ var areaHeight = (this._contentBox.h -
+ (this.getHeaderHeight() + this.getFooterHeight() +
+ dojo._getPadBorderExtents(this.iframe.parentNode).h +
+ dojo._getMarginExtents(this.iframe.parentNode).h));
+ this.editingArea.style.height = areaHeight + "px";
+ if(this.iframe){
+ this.iframe.style.height="100%";
+ }
+ this._layoutMode = true;
+ },
+ _onIEMouseDown: function(/*Event*/ e){
+ // summary:
+ // IE only to prevent 2 clicks to focus
+ // tags:
+ // private
+ var outsideClientArea;
+ // IE 8's componentFromPoint is broken, which is a shame since it
+ // was smaller code, but oh well. We have to do this brute force
+ // to detect if the click was scroller or not.
+ var b = this.document.body;
+ var clientWidth = b.clientWidth;
+ var clientHeight = b.clientHeight;
+ var clientLeft = b.clientLeft;
+ var offsetWidth = b.offsetWidth;
+ var offsetHeight = b.offsetHeight;
+ var offsetLeft = b.offsetLeft;
+
+ //Check for vertical scroller click.
+ bodyDir = b.dir ? b.dir.toLowerCase() : "";
+ if(bodyDir != "rtl"){
+ if(clientWidth < offsetWidth && e.x > clientWidth && e.x < offsetWidth){
+ // Check the click was between width and offset width, if so, scroller
+ outsideClientArea = true;
+ }
+ }else{
+ // RTL mode, we have to go by the left offsets.
+ if(e.x < clientLeft && e.x > offsetLeft){
+ // Check the click was between width and offset width, if so, scroller
+ outsideClientArea = true;
+ }
+ }
+ if(!outsideClientArea){
+ // Okay, might be horiz scroller, check that.
+ if(clientHeight < offsetHeight && e.y > clientHeight && e.y < offsetHeight){
+ // Horizontal scroller.
+ outsideClientArea = true;
+ }
+ }
+ if(!outsideClientArea){
+ delete this._cursorToStart; // Remove the force to cursor to start position.
+ delete this._savedSelection; // new mouse position overrides old selection
+ if(e.target.tagName == "BODY"){
+ setTimeout(dojo.hitch(this, "placeCursorAtEnd"), 0);
+ }
+ this.inherited(arguments);
+ }
+ },
+ onBeforeActivate: function(e){
+ this._restoreSelection();
+ },
+ onBeforeDeactivate: function(e){
+ // summary:
+ // Called on IE right before focus is lost. Saves the selected range.
+ // tags:
+ // private
+ if(this.customUndo){
+ this.endEditing(true);
+ }
+ //in IE, the selection will be lost when other elements get focus,
+ //let's save focus before the editor is deactivated
+ if(e.target.tagName != "BODY"){
+ this._saveSelection();
+ }
+ //console.log('onBeforeDeactivate',this);
+ },
+
+ /* beginning of custom undo/redo support */
+
+ // customUndo: Boolean
+ // Whether we shall use custom undo/redo support instead of the native
+ // browser support. By default, we now use custom undo. It works better
+ // than native browser support and provides a consistent behavior across
+ // browsers with a minimal performance hit. We already had the hit on
+ // the slowest browser, IE, anyway.
+ customUndo: true,
+
+ // editActionInterval: Integer
+ // When using customUndo, not every keystroke will be saved as a step.
+ // Instead typing (including delete) will be grouped together: after
+ // a user stops typing for editActionInterval seconds, a step will be
+ // saved; if a user resume typing within editActionInterval seconds,
+ // the timeout will be restarted. By default, editActionInterval is 3
+ // seconds.
+ editActionInterval: 3,
+
+ beginEditing: function(cmd){
+ // summary:
+ // Called to note that the user has started typing alphanumeric characters, if it's not already noted.
+ // Deals with saving undo; see editActionInterval parameter.
+ // tags:
+ // private
+ if(!this._inEditing){
+ this._inEditing=true;
+ this._beginEditing(cmd);
+ }
+ if(this.editActionInterval>0){
+ if(this._editTimer){
+ clearTimeout(this._editTimer);
+ }
+ this._editTimer = setTimeout(dojo.hitch(this, this.endEditing), this._editInterval);
+ }
+ },
+
+ // TODO: declaring these in the prototype is meaningless, just create in the constructor/postCreate
+ _steps:[],
+ _undoedSteps:[],
+
+ execCommand: function(cmd){
+ // summary:
+ // Main handler for executing any commands to the editor, like paste, bold, etc.
+ // Called by plugins, but not meant to be called by end users.
+ // tags:
+ // protected
+ if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){
+ return this[cmd]();
+ }else{
+ if(this.customUndo){
+ this.endEditing();
+ this._beginEditing();
+ }
+ var r;
+ var isClipboard = /copy|cut|paste/.test(cmd);
+ try{
+ r = this.inherited(arguments);
+ if(dojo.isWebKit && isClipboard && !r){ //see #4598: webkit does not guarantee clipboard support from js
+ throw { code: 1011 }; // throw an object like Mozilla's error
+ }
+ }catch(e){
+ //TODO: when else might we get an exception? Do we need the Mozilla test below?
+ if(e.code == 1011 /* Mozilla: service denied */ && isClipboard){
+ // Warn user of platform limitation. Cannot programmatically access clipboard. See ticket #4136
+ var sub = dojo.string.substitute,
+ accel = {cut:'X', copy:'C', paste:'V'};
+ alert(sub(this.commands.systemShortcut,
+ [this.commands[cmd], sub(this.commands[dojo.isMac ? 'appleKey' : 'ctrlKey'], [accel[cmd]])]));
+ }
+ r = false;
+ }
+ if(this.customUndo){
+ this._endEditing();
+ }
+ return r;
+ }
+ },
+ queryCommandEnabled: function(cmd){
+ // summary:
+ // Returns true if specified editor command is enabled.
+ // Used by the plugins to know when to highlight/not highlight buttons.
+ // tags:
+ // protected
+ if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){
+ return cmd == 'undo' ? (this._steps.length > 1) : (this._undoedSteps.length > 0);
+ }else{
+ return this.inherited(arguments);
+ }
+ },
+ _moveToBookmark: function(b){
+ // summary:
+ // Selects the text specified in bookmark b
+ // tags:
+ // private
+ var bookmark = b.mark;
+ var mark = b.mark;
+ var col = b.isCollapsed;
+ var r, sNode, eNode, sel;
+ if(mark){
+ if(dojo.isIE < 9){
+ if(dojo.isArray(mark)){
+ //IE CONTROL, have to use the native bookmark.
+ bookmark = [];
+ dojo.forEach(mark,function(n){
+ bookmark.push(dijit.range.getNode(n,this.editNode));
+ },this);
+ dojo.withGlobal(this.window,'moveToBookmark',dijit,[{mark: bookmark, isCollapsed: col}]);
+ }else{
+ if(mark.startContainer && mark.endContainer){
+ // Use the pseudo WC3 range API. This works better for positions
+ // than the IE native bookmark code.
+ sel = dijit.range.getSelection(this.window);
+ if(sel && sel.removeAllRanges){
+ sel.removeAllRanges();
+ r = dijit.range.create(this.window);
+ sNode = dijit.range.getNode(mark.startContainer,this.editNode);
+ eNode = dijit.range.getNode(mark.endContainer,this.editNode);
+ if(sNode && eNode){
+ // Okay, we believe we found the position, so add it into the selection
+ // There are cases where it may not be found, particularly in undo/redo, when
+ // IE changes the underlying DOM on us (wraps text in a <p> tag or similar.
+ // So, in those cases, don't bother restoring selection.
+ r.setStart(sNode,mark.startOffset);
+ r.setEnd(eNode,mark.endOffset);
+ sel.addRange(r);
+ }
+ }
+ }
+ }
+ }else{//w3c range
+ sel = dijit.range.getSelection(this.window);
+ if(sel && sel.removeAllRanges){
+ sel.removeAllRanges();
+ r = dijit.range.create(this.window);
+ sNode = dijit.range.getNode(mark.startContainer,this.editNode);
+ eNode = dijit.range.getNode(mark.endContainer,this.editNode);
+ if(sNode && eNode){
+ // Okay, we believe we found the position, so add it into the selection
+ // There are cases where it may not be found, particularly in undo/redo, when
+ // formatting as been done and so on, so don't restore selection then.
+ r.setStart(sNode,mark.startOffset);
+ r.setEnd(eNode,mark.endOffset);
+ sel.addRange(r);
+ }
+ }
+ }
+ }
+ },
+ _changeToStep: function(from, to){
+ // summary:
+ // Reverts editor to "to" setting, from the undo stack.
+ // tags:
+ // private
+ this.setValue(to.text);
+ var b=to.bookmark;
+ if(!b){ return; }
+ this._moveToBookmark(b);
+ },
+ undo: function(){
+ // summary:
+ // Handler for editor undo (ex: ctrl-z) operation
+ // tags:
+ // private
+ //console.log('undo');
+ var ret = false;
+ if(!this._undoRedoActive){
+ this._undoRedoActive = true;
+ this.endEditing(true);
+ var s=this._steps.pop();
+ if(s && this._steps.length>0){
+ this.focus();
+ this._changeToStep(s,this._steps[this._steps.length-1]);
+ this._undoedSteps.push(s);
+ this.onDisplayChanged();
+ delete this._undoRedoActive;
+ ret = true;
+ }
+ delete this._undoRedoActive;
+ }
+ return ret;
+ },
+ redo: function(){
+ // summary:
+ // Handler for editor redo (ex: ctrl-y) operation
+ // tags:
+ // private
+ //console.log('redo');
+ var ret = false;
+ if(!this._undoRedoActive){
+ this._undoRedoActive = true;
+ this.endEditing(true);
+ var s=this._undoedSteps.pop();
+ if(s && this._steps.length>0){
+ this.focus();
+ this._changeToStep(this._steps[this._steps.length-1],s);
+ this._steps.push(s);
+ this.onDisplayChanged();
+ ret = true;
+ }
+ delete this._undoRedoActive;
+ }
+ return ret;
+ },
+ endEditing: function(ignore_caret){
+ // summary:
+ // Called to note that the user has stopped typing alphanumeric characters, if it's not already noted.
+ // Deals with saving undo; see editActionInterval parameter.
+ // tags:
+ // private
+ if(this._editTimer){
+ clearTimeout(this._editTimer);
+ }
+ if(this._inEditing){
+ this._endEditing(ignore_caret);
+ this._inEditing=false;
+ }
+ },
+
+ _getBookmark: function(){
+ // summary:
+ // Get the currently selected text
+ // tags:
+ // protected
+ var b=dojo.withGlobal(this.window,dijit.getBookmark);
+ var tmp=[];
+ if(b && b.mark){
+ var mark = b.mark;
+ if(dojo.isIE < 9){
+ // Try to use the pseudo range API on IE for better accuracy.
+ var sel = dijit.range.getSelection(this.window);
+ if(!dojo.isArray(mark)){
+ if(sel){
+ var range;
+ if(sel.rangeCount){
+ range = sel.getRangeAt(0);
+ }
+ if(range){
+ b.mark = range.cloneRange();
+ }else{
+ b.mark = dojo.withGlobal(this.window,dijit.getBookmark);
+ }
+ }
+ }else{
+ // Control ranges (img, table, etc), handle differently.
+ dojo.forEach(b.mark,function(n){
+ tmp.push(dijit.range.getIndex(n,this.editNode).o);
+ },this);
+ b.mark = tmp;
+ }
+ }
+ try{
+ if(b.mark && b.mark.startContainer){
+ tmp=dijit.range.getIndex(b.mark.startContainer,this.editNode).o;
+ b.mark={startContainer:tmp,
+ startOffset:b.mark.startOffset,
+ endContainer:b.mark.endContainer===b.mark.startContainer?tmp:dijit.range.getIndex(b.mark.endContainer,this.editNode).o,
+ endOffset:b.mark.endOffset};
+ }
+ }catch(e){
+ b.mark = null;
+ }
+ }
+ return b;
+ },
+ _beginEditing: function(cmd){
+ // summary:
+ // Called when the user starts typing alphanumeric characters.
+ // Deals with saving undo; see editActionInterval parameter.
+ // tags:
+ // private
+ if(this._steps.length === 0){
+ // You want to use the editor content without post filtering
+ // to make sure selection restores right for the 'initial' state.
+ // and undo is called. So not using this.value, as it was 'processed'
+ // and the line-up for selections may have been altered.
+ this._steps.push({'text':dijit._editor.getChildrenHtml(this.editNode),'bookmark':this._getBookmark()});
+ }
+ },
+ _endEditing: function(ignore_caret){
+ // summary:
+ // Called when the user stops typing alphanumeric characters.
+ // Deals with saving undo; see editActionInterval parameter.
+ // tags:
+ // private
+ // Avoid filtering to make sure selections restore.
+ var v = dijit._editor.getChildrenHtml(this.editNode);
+
+ this._undoedSteps=[];//clear undoed steps
+ this._steps.push({text: v, bookmark: this._getBookmark()});
+ },
+ onKeyDown: function(e){
+ // summary:
+ // Handler for onkeydown event.
+ // tags:
+ // private
+
+ //We need to save selection if the user TAB away from this editor
+ //no need to call _saveSelection for IE, as that will be taken care of in onBeforeDeactivate
+ if(!dojo.isIE && !this.iframe && e.keyCode == dojo.keys.TAB && !this.tabIndent){
+ this._saveSelection();
+ }
+ if(!this.customUndo){
+ this.inherited(arguments);
+ return;
+ }
+ var k = e.keyCode, ks = dojo.keys;
+ if(e.ctrlKey && !e.altKey){//undo and redo only if the special right Alt + z/y are not pressed #5892
+ if(k == 90 || k == 122){ //z
+ dojo.stopEvent(e);
+ this.undo();
+ return;
+ }else if(k == 89 || k == 121){ //y
+ dojo.stopEvent(e);
+ this.redo();
+ return;
+ }
+ }
+ this.inherited(arguments);
+
+ switch(k){
+ case ks.ENTER:
+ case ks.BACKSPACE:
+ case ks.DELETE:
+ this.beginEditing();
+ break;
+ case 88: //x
+ case 86: //v
+ if(e.ctrlKey && !e.altKey && !e.metaKey){
+ this.endEditing();//end current typing step if any
+ if(e.keyCode == 88){
+ this.beginEditing('cut');
+ //use timeout to trigger after the cut is complete
+ setTimeout(dojo.hitch(this, this.endEditing), 1);
+ }else{
+ this.beginEditing('paste');
+ //use timeout to trigger after the paste is complete
+ setTimeout(dojo.hitch(this, this.endEditing), 1);
+ }
+ break;
+ }
+ //pass through
+ default:
+ if(!e.ctrlKey && !e.altKey && !e.metaKey && (e.keyCode<dojo.keys.F1 || e.keyCode>dojo.keys.F15)){
+ this.beginEditing();
+ break;
+ }
+ //pass through
+ case ks.ALT:
+ this.endEditing();
+ break;
+ case ks.UP_ARROW:
+ case ks.DOWN_ARROW:
+ case ks.LEFT_ARROW:
+ case ks.RIGHT_ARROW:
+ case ks.HOME:
+ case ks.END:
+ case ks.PAGE_UP:
+ case ks.PAGE_DOWN:
+ this.endEditing(true);
+ break;
+ //maybe ctrl+backspace/delete, so don't endEditing when ctrl is pressed
+ case ks.CTRL:
+ case ks.SHIFT:
+ case ks.TAB:
+ break;
+ }
+ },
+ _onBlur: function(){
+ // summary:
+ // Called from focus manager when focus has moved away from this editor
+ // tags:
+ // protected
+
+ //this._saveSelection();
+ this.inherited(arguments);
+ this.endEditing(true);
+ },
+ _saveSelection: function(){
+ // summary:
+ // Save the currently selected text in _savedSelection attribute
+ // tags:
+ // private
+ try{
+ this._savedSelection=this._getBookmark();
+ }catch(e){ /* Squelch any errors that occur if selection save occurs due to being hidden simultaniously. */}
+ },
+ _restoreSelection: function(){
+ // summary:
+ // Re-select the text specified in _savedSelection attribute;
+ // see _saveSelection().
+ // tags:
+ // private
+ if(this._savedSelection){
+ // Clear off cursor to start, we're deliberately going to a selection.
+ delete this._cursorToStart;
+ // only restore the selection if the current range is collapsed
+ // if not collapsed, then it means the editor does not lose
+ // selection and there is no need to restore it
+ if(dojo.withGlobal(this.window,'isCollapsed',dijit)){
+ this._moveToBookmark(this._savedSelection);
+ }
+ delete this._savedSelection;
+ }
+ },
+
+ onClick: function(){
+ // summary:
+ // Handler for when editor is clicked
+ // tags:
+ // protected
+ this.endEditing(true);
+ this.inherited(arguments);
+ },
+
+ replaceValue: function(/*String*/ html){
+ // summary:
+ // over-ride of replaceValue to support custom undo and stack maintainence.
+ // tags:
+ // protected
+ if(!this.customUndo){
+ this.inherited(arguments);
+ }else{
+ if(this.isClosed){
+ this.setValue(html);
+ }else{
+ this.beginEditing();
+ if(!html){
+ html = "&nbsp;"
+ }
+ this.setValue(html);
+ this.endEditing();
+ }
+ }
+ },
+
+ _setDisabledAttr: function(/*Boolean*/ value){
+ var disableFunc = dojo.hitch(this, function(){
+ if((!this.disabled && value) || (!this._buttonEnabledPlugins && value)){
+ // Disable editor: disable all enabled buttons and remember that list
+ dojo.forEach(this._plugins, function(p){
+ p.set("disabled", true);
+ });
+ }else if(this.disabled && !value){
+ // Restore plugins to being active.
+ dojo.forEach(this._plugins, function(p){
+ p.set("disabled", false);
+ });
+ }
+ });
+ this.setValueDeferred.addCallback(disableFunc);
+ this.inherited(arguments);
+ },
+
+ _setStateClass: function(){
+ try{
+ this.inherited(arguments);
+
+ // Let theme set the editor's text color based on editor enabled/disabled state.
+ // We need to jump through hoops because the main document (where the theme CSS is)
+ // is separate from the iframe's document.
+ if(this.document && this.document.body){
+ dojo.style(this.document.body, "color", dojo.style(this.iframe, "color"));
+ }
+ }catch(e){ /* Squelch any errors caused by focus change if hidden during a state change */}
+ }
+ }
+);
+
+// Register the "default plugins", ie, the built-in editor commands
+dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){
+ if(o.plugin){ return; }
+ var args = o.args, p;
+ var _p = dijit._editor._Plugin;
+ var name = args.name;
+ switch(name){
+ case "undo": case "redo": case "cut": case "copy": case "paste": case "insertOrderedList":
+ case "insertUnorderedList": case "indent": case "outdent": case "justifyCenter":
+ case "justifyFull": case "justifyLeft": case "justifyRight": case "delete":
+ case "selectAll": case "removeFormat": case "unlink":
+ case "insertHorizontalRule":
+ p = new _p({ command: name });
+ break;
+
+ case "bold": case "italic": case "underline": case "strikethrough":
+ case "subscript": case "superscript":
+ p = new _p({ buttonClass: dijit.form.ToggleButton, command: name });
+ break;
+ case "|":
+ p = new _p({ button: new dijit.ToolbarSeparator(), setEditor: function(editor) {this.editor = editor;} });
+ }
+// console.log('name',name,p);
+ o.plugin=p;
});
+
}