]> git.wh0rd.org Git - tt-rss.git/blob - lib/dijit/Editor.js.uncompressed.js
make precache_headlines_idle() start slower
[tt-rss.git] / lib / dijit / Editor.js.uncompressed.js
1 define("dijit/Editor", [
2         "dojo/_base/array", // array.forEach
3         "dojo/_base/declare", // declare
4         "dojo/_base/Deferred", // Deferred
5         "dojo/i18n", // i18n.getLocalization
6         "dojo/dom-attr", // domAttr.set
7         "dojo/dom-class", // domClass.add
8         "dojo/dom-geometry",
9         "dojo/dom-style", // domStyle.set, get
10         "dojo/_base/event", // event.stop
11         "dojo/keys", // keys.F1 keys.F15 keys.TAB
12         "dojo/_base/lang", // lang.getObject lang.hitch
13         "dojo/_base/sniff", // has("ie") has("mac") has("webkit")
14         "dojo/string", // string.substitute
15         "dojo/topic", // topic.publish()
16         "dojo/_base/window", // win.withGlobal
17         "./_base/focus",        // dijit.getBookmark()
18         "./_Container",
19         "./Toolbar",
20         "./ToolbarSeparator",
21         "./layout/_LayoutWidget",
22         "./form/ToggleButton",
23         "./_editor/_Plugin",
24         "./_editor/plugins/EnterKeyHandling",
25         "./_editor/html",
26         "./_editor/range",
27         "./_editor/RichText",
28         ".",    // dijit._scopeName
29         "dojo/i18n!./_editor/nls/commands"
30 ], function(array, declare, Deferred, i18n, domAttr, domClass, domGeometry, domStyle,
31                         event, keys, lang, has, string, topic, win,
32                         focusBase, _Container, Toolbar, ToolbarSeparator, _LayoutWidget, ToggleButton,
33                         _Plugin, EnterKeyHandling, html, rangeapi, RichText, dijit){
34
35         // module:
36         //              dijit/Editor
37         // summary:
38         //              A rich text Editing widget
39
40         var Editor = declare("dijit.Editor", RichText, {
41                 // summary:
42                 //              A rich text Editing widget
43                 //
44                 // description:
45                 //              This widget provides basic WYSIWYG editing features, based on the browser's
46                 //              underlying rich text editing capability, accompanied by a toolbar (`dijit.Toolbar`).
47                 //              A plugin model is available to extend the editor's capabilities as well as the
48                 //              the options available in the toolbar.  Content generation may vary across
49                 //              browsers, and clipboard operations may have different results, to name
50                 //              a few limitations.  Note: this widget should not be used with the HTML
51                 //              <TEXTAREA> tag -- see dijit._editor.RichText for details.
52
53                 // plugins: [const] Object[]
54                 //              A list of plugin names (as strings) or instances (as objects)
55                 //              for this widget.
56                 //
57                 //              When declared in markup, it might look like:
58                 //      |       plugins="['bold',{name:'dijit._editor.plugins.FontChoice', command:'fontName', generic:true}]"
59                 plugins: null,
60
61                 // extraPlugins: [const] Object[]
62                 //              A list of extra plugin names which will be appended to plugins array
63                 extraPlugins: null,
64
65                 constructor: function(){
66                         // summary:
67                         //              Runs on widget initialization to setup arrays etc.
68                         // tags:
69                         //              private
70
71                         if(!lang.isArray(this.plugins)){
72                                 this.plugins=["undo","redo","|","cut","copy","paste","|","bold","italic","underline","strikethrough","|",
73                                 "insertOrderedList","insertUnorderedList","indent","outdent","|","justifyLeft","justifyRight","justifyCenter","justifyFull",
74                                 EnterKeyHandling /*, "createLink"*/];
75                         }
76
77                         this._plugins=[];
78                         this._editInterval = this.editActionInterval * 1000;
79
80                         //IE will always lose focus when other element gets focus, while for FF and safari,
81                         //when no iframe is used, focus will be lost whenever another element gets focus.
82                         //For IE, we can connect to onBeforeDeactivate, which will be called right before
83                         //the focus is lost, so we can obtain the selected range. For other browsers,
84                         //no equivalent of onBeforeDeactivate, so we need to do two things to make sure
85                         //selection is properly saved before focus is lost: 1) when user clicks another
86                         //element in the page, in which case we listen to mousedown on the entire page and
87                         //see whether user clicks out of a focus editor, if so, save selection (focus will
88                         //only lost after onmousedown event is fired, so we can obtain correct caret pos.)
89                         //2) when user tabs away from the editor, which is handled in onKeyDown below.
90                         if(has("ie")){
91                                 this.events.push("onBeforeDeactivate");
92                                 this.events.push("onBeforeActivate");
93                         }
94                 },
95
96                 postMixInProperties: function(){
97                         // summary:
98                         //      Extension to make sure a deferred is in place before certain functions
99                         //      execute, like making sure all the plugins are properly inserted.
100
101                         // Set up a deferred so that the value isn't applied to the editor
102                         // until all the plugins load, needed to avoid timing condition
103                         // reported in #10537.
104                         this.setValueDeferred = new Deferred();
105                         this.inherited(arguments);
106                 },
107
108                 postCreate: function(){
109                         //for custom undo/redo, if enabled.
110                         this._steps=this._steps.slice(0);
111                         this._undoedSteps=this._undoedSteps.slice(0);
112
113                         if(lang.isArray(this.extraPlugins)){
114                                 this.plugins=this.plugins.concat(this.extraPlugins);
115                         }
116
117                         this.inherited(arguments);
118
119                         this.commands = i18n.getLocalization("dijit._editor", "commands", this.lang);
120
121                         if(!this.toolbar){
122                                 // if we haven't been assigned a toolbar, create one
123                                 this.toolbar = new Toolbar({
124                                         dir: this.dir,
125                                         lang: this.lang
126                                 });
127                                 this.header.appendChild(this.toolbar.domNode);
128                         }
129
130                         array.forEach(this.plugins, this.addPlugin, this);
131
132                         // Okay, denote the value can now be set.
133                         this.setValueDeferred.callback(true);
134
135                         domClass.add(this.iframe.parentNode, "dijitEditorIFrameContainer");
136                         domClass.add(this.iframe, "dijitEditorIFrame");
137                         domAttr.set(this.iframe, "allowTransparency", true);
138
139                         if(has("webkit")){
140                                 // Disable selecting the entire editor by inadvertent double-clicks.
141                                 // on buttons, title bar, etc.  Otherwise clicking too fast on
142                                 // a button such as undo/redo selects the entire editor.
143                                 domStyle.set(this.domNode, "KhtmlUserSelect", "none");
144                         }
145                         this.toolbar.startup();
146                         this.onNormalizedDisplayChanged(); //update toolbar button status
147                 },
148                 destroy: function(){
149                         array.forEach(this._plugins, function(p){
150                                 if(p && p.destroy){
151                                         p.destroy();
152                                 }
153                         });
154                         this._plugins=[];
155                         this.toolbar.destroyRecursive();
156                         delete this.toolbar;
157                         this.inherited(arguments);
158                 },
159                 addPlugin: function(/*String||Object||Function*/plugin, /*Integer?*/index){
160                         // summary:
161                         //              takes a plugin name as a string or a plugin instance and
162                         //              adds it to the toolbar and associates it with this editor
163                         //              instance. The resulting plugin is added to the Editor's
164                         //              plugins array. If index is passed, it's placed in the plugins
165                         //              array at that index. No big magic, but a nice helper for
166                         //              passing in plugin names via markup.
167                         //
168                         // plugin: String, args object, plugin instance, or plugin constructor
169                         //
170                         // args:
171                         //              This object will be passed to the plugin constructor
172                         //
173                         // index: Integer
174                         //              Used when creating an instance from
175                         //              something already in this.plugins. Ensures that the new
176                         //              instance is assigned to this.plugins at that index.
177                         var args=lang.isString(plugin)?{name:plugin}:lang.isFunction(plugin)?{ctor:plugin}:plugin;
178                         if(!args.setEditor){
179                                 var o={"args":args,"plugin":null,"editor":this};
180                                 if(args.name){
181                                         // search registry for a plugin factory matching args.name, if it's not there then
182                                         // fallback to 1.0 API:
183                                         // ask all loaded plugin modules to fill in o.plugin if they can (ie, if they implement args.name)
184                                         // remove fallback for 2.0.
185                                         if(_Plugin.registry[args.name]){
186                                                 o.plugin = _Plugin.registry[args.name](args);
187                                         }else{
188                                                 topic.publish(dijit._scopeName + ".Editor.getPlugin", o);       // publish
189                                         }
190                                 }
191                                 if(!o.plugin){
192                                         var pc = args.ctor || lang.getObject(args.name);
193                                         if(pc){
194                                                 o.plugin=new pc(args);
195                                         }
196                                 }
197                                 if(!o.plugin){
198                                         console.warn('Cannot find plugin',plugin);
199                                         return;
200                                 }
201                                 plugin=o.plugin;
202                         }
203                         if(arguments.length > 1){
204                                 this._plugins[index] = plugin;
205                         }else{
206                                 this._plugins.push(plugin);
207                         }
208                         plugin.setEditor(this);
209                         if(lang.isFunction(plugin.setToolbar)){
210                                 plugin.setToolbar(this.toolbar);
211                         }
212                 },
213
214                 //the following 2 functions are required to make the editor play nice under a layout widget, see #4070
215
216                 resize: function(size){
217                         // summary:
218                         //              Resize the editor to the specified size, see `dijit.layout._LayoutWidget.resize`
219                         if(size){
220                                 // we've been given a height/width for the entire editor (toolbar + contents), calls layout()
221                                 // to split the allocated size between the toolbar and the contents
222                                 _LayoutWidget.prototype.resize.apply(this, arguments);
223                         }
224                         /*
225                         else{
226                                 // do nothing, the editor is already laid out correctly.   The user has probably specified
227                                 // the height parameter, which was used to set a size on the iframe
228                         }
229                         */
230                 },
231                 layout: function(){
232                         // summary:
233                         //              Called from `dijit.layout._LayoutWidget.resize`.  This shouldn't be called directly
234                         // tags:
235                         //              protected
236
237                         // Converts the iframe (or rather the <div> surrounding it) to take all the available space
238                         // except what's needed for the header (toolbars) and footer (breadcrumbs, etc).
239                         // A class was added to the iframe container and some themes style it, so we have to
240                         // calc off the added margins and padding too. See tracker: #10662
241                         var areaHeight = (this._contentBox.h -
242                                 (this.getHeaderHeight() + this.getFooterHeight() +
243                                  domGeometry.getPadBorderExtents(this.iframe.parentNode).h +
244                                  domGeometry.getMarginExtents(this.iframe.parentNode).h));
245                         this.editingArea.style.height = areaHeight + "px";
246                         if(this.iframe){
247                                 this.iframe.style.height="100%";
248                         }
249                         this._layoutMode = true;
250                 },
251
252                 _onIEMouseDown: function(/*Event*/ e){
253                         // summary:
254                         //              IE only to prevent 2 clicks to focus
255                         // tags:
256                         //              private
257                         var outsideClientArea;
258                         // IE 8's componentFromPoint is broken, which is a shame since it
259                         // was smaller code, but oh well.  We have to do this brute force
260                         // to detect if the click was scroller or not.
261                         var b = this.document.body;
262                         var clientWidth = b.clientWidth;
263                         var clientHeight = b.clientHeight;
264                         var clientLeft = b.clientLeft;
265                         var offsetWidth = b.offsetWidth;
266                         var offsetHeight = b.offsetHeight;
267                         var offsetLeft = b.offsetLeft;
268
269                         //Check for vertical scroller click.
270                         if(/^rtl$/i.test(b.dir || "")){
271                                 if(clientWidth < offsetWidth && e.x > clientWidth && e.x < offsetWidth){
272                                         // Check the click was between width and offset width, if so, scroller
273                                         outsideClientArea = true;
274                                 }
275                         }else{
276                                 // RTL mode, we have to go by the left offsets.
277                                 if(e.x < clientLeft && e.x > offsetLeft){
278                                         // Check the click was between width and offset width, if so, scroller
279                                         outsideClientArea = true;
280                                 }
281                         }
282                         if(!outsideClientArea){
283                                 // Okay, might be horiz scroller, check that.
284                                 if(clientHeight < offsetHeight && e.y > clientHeight && e.y < offsetHeight){
285                                         // Horizontal scroller.
286                                         outsideClientArea = true;
287                                 }
288                         }
289                         if(!outsideClientArea){
290                                 delete this._cursorToStart; // Remove the force to cursor to start position.
291                                 delete this._savedSelection; // new mouse position overrides old selection
292                                 if(e.target.tagName == "BODY"){
293                                         setTimeout(lang.hitch(this, "placeCursorAtEnd"), 0);
294                                 }
295                                 this.inherited(arguments);
296                         }
297                 },
298                 onBeforeActivate: function(){
299                         this._restoreSelection();
300                 },
301                 onBeforeDeactivate: function(e){
302                         // summary:
303                         //              Called on IE right before focus is lost.   Saves the selected range.
304                         // tags:
305                         //              private
306                         if(this.customUndo){
307                                 this.endEditing(true);
308                         }
309                         //in IE, the selection will be lost when other elements get focus,
310                         //let's save focus before the editor is deactivated
311                         if(e.target.tagName != "BODY"){
312                                 this._saveSelection();
313                         }
314                         //console.log('onBeforeDeactivate',this);
315                 },
316
317                 /* beginning of custom undo/redo support */
318
319                 // customUndo: Boolean
320                 //              Whether we shall use custom undo/redo support instead of the native
321                 //              browser support. By default, we now use custom undo.  It works better
322                 //              than native browser support and provides a consistent behavior across
323                 //              browsers with a minimal performance hit.  We already had the hit on
324                 //              the slowest browser, IE, anyway.
325                 customUndo: true,
326
327                 // editActionInterval: Integer
328                 //              When using customUndo, not every keystroke will be saved as a step.
329                 //              Instead typing (including delete) will be grouped together: after
330                 //              a user stops typing for editActionInterval seconds, a step will be
331                 //              saved; if a user resume typing within editActionInterval seconds,
332                 //              the timeout will be restarted. By default, editActionInterval is 3
333                 //              seconds.
334                 editActionInterval: 3,
335
336                 beginEditing: function(cmd){
337                         // summary:
338                         //              Called to note that the user has started typing alphanumeric characters, if it's not already noted.
339                         //              Deals with saving undo; see editActionInterval parameter.
340                         // tags:
341                         //              private
342                         if(!this._inEditing){
343                                 this._inEditing=true;
344                                 this._beginEditing(cmd);
345                         }
346                         if(this.editActionInterval>0){
347                                 if(this._editTimer){
348                                         clearTimeout(this._editTimer);
349                                 }
350                                 this._editTimer = setTimeout(lang.hitch(this, this.endEditing), this._editInterval);
351                         }
352                 },
353
354                 // TODO: declaring these in the prototype is meaningless, just create in the constructor/postCreate
355                 _steps:[],
356                 _undoedSteps:[],
357
358                 execCommand: function(cmd){
359                         // summary:
360                         //              Main handler for executing any commands to the editor, like paste, bold, etc.
361                         //      Called by plugins, but not meant to be called by end users.
362                         // tags:
363                         //              protected
364                         if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){
365                                 return this[cmd]();
366                         }else{
367                                 if(this.customUndo){
368                                         this.endEditing();
369                                         this._beginEditing();
370                                 }
371                                 var r = this.inherited(arguments);
372                                 if(this.customUndo){
373                                         this._endEditing();
374                                 }
375                                 return r;
376                         }
377                 },
378
379                 _pasteImpl: function(){
380                         // summary:
381                         //              Over-ride of paste command control to make execCommand cleaner
382                         // tags:
383                         //              Protected
384                         return this._clipboardCommand("paste");
385                 },
386
387                 _cutImpl: function(){
388                         // summary:
389                         //              Over-ride of cut command control to make execCommand cleaner
390                         // tags:
391                         //              Protected
392                         return this._clipboardCommand("cut");
393                 },
394
395                 _copyImpl: function(){
396                         // summary:
397                         //              Over-ride of copy command control to make execCommand cleaner
398                         // tags:
399                         //              Protected
400                         return this._clipboardCommand("copy");
401                 },
402
403                 _clipboardCommand: function(cmd){
404                         // summary:
405                         //              Function to handle processing clipboard commands (or at least try to).
406                         // tags:
407                         //              Private
408                         var r;
409                         try{
410                                 // Try to exec the superclass exec-command and see if it works.
411                                 r = this.document.execCommand(cmd, false, null);
412                                 if(has("webkit") && !r){ //see #4598: webkit does not guarantee clipboard support from js
413                                         throw { code: 1011 }; // throw an object like Mozilla's error
414                                 }
415                         }catch(e){
416                                 //TODO: when else might we get an exception?  Do we need the Mozilla test below?
417                                 if(e.code == 1011 /* Mozilla: service denied */){
418                                         // Warn user of platform limitation.  Cannot programmatically access clipboard. See ticket #4136
419                                         var sub = string.substitute,
420                                                 accel = {cut:'X', copy:'C', paste:'V'};
421                                         alert(sub(this.commands.systemShortcut,
422                                                 [this.commands[cmd], sub(this.commands[has("mac") ? 'appleKey' : 'ctrlKey'], [accel[cmd]])]));
423                                 }
424                                 r = false;
425                         }
426                         return r;
427                 },
428
429                 queryCommandEnabled: function(cmd){
430                         // summary:
431                         //              Returns true if specified editor command is enabled.
432                         //      Used by the plugins to know when to highlight/not highlight buttons.
433                         // tags:
434                         //              protected
435                         if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){
436                                 return cmd == 'undo' ? (this._steps.length > 1) : (this._undoedSteps.length > 0);
437                         }else{
438                                 return this.inherited(arguments);
439                         }
440                 },
441                 _moveToBookmark: function(b){
442                         // summary:
443                         //              Selects the text specified in bookmark b
444                         // tags:
445                         //              private
446                         var bookmark = b.mark;
447                         var mark = b.mark;
448                         var col = b.isCollapsed;
449                         var r, sNode, eNode, sel;
450                         if(mark){
451                                 if(has("ie") < 9){
452                                         if(lang.isArray(mark)){
453                                                 //IE CONTROL, have to use the native bookmark.
454                                                 bookmark = [];
455                                                 array.forEach(mark,function(n){
456                                                         bookmark.push(rangeapi.getNode(n,this.editNode));
457                                                 },this);
458                                                 win.withGlobal(this.window,'moveToBookmark',dijit,[{mark: bookmark, isCollapsed: col}]);
459                                         }else{
460                                                 if(mark.startContainer && mark.endContainer){
461                                                         // Use the pseudo WC3 range API.  This works better for positions
462                                                         // than the IE native bookmark code.
463                                                         sel = rangeapi.getSelection(this.window);
464                                                         if(sel && sel.removeAllRanges){
465                                                                 sel.removeAllRanges();
466                                                                 r = rangeapi.create(this.window);
467                                                                 sNode = rangeapi.getNode(mark.startContainer,this.editNode);
468                                                                 eNode = rangeapi.getNode(mark.endContainer,this.editNode);
469                                                                 if(sNode && eNode){
470                                                                         // Okay, we believe we found the position, so add it into the selection
471                                                                         // There are cases where it may not be found, particularly in undo/redo, when
472                                                                         // IE changes the underlying DOM on us (wraps text in a <p> tag or similar.
473                                                                         // So, in those cases, don't bother restoring selection.
474                                                                         r.setStart(sNode,mark.startOffset);
475                                                                         r.setEnd(eNode,mark.endOffset);
476                                                                         sel.addRange(r);
477                                                                 }
478                                                         }
479                                                 }
480                                         }
481                                 }else{//w3c range
482                                         sel = rangeapi.getSelection(this.window);
483                                         if(sel && sel.removeAllRanges){
484                                                 sel.removeAllRanges();
485                                                 r = rangeapi.create(this.window);
486                                                 sNode = rangeapi.getNode(mark.startContainer,this.editNode);
487                                                 eNode = rangeapi.getNode(mark.endContainer,this.editNode);
488                                                 if(sNode && eNode){
489                                                         // Okay, we believe we found the position, so add it into the selection
490                                                         // There are cases where it may not be found, particularly in undo/redo, when
491                                                         // formatting as been done and so on, so don't restore selection then.
492                                                         r.setStart(sNode,mark.startOffset);
493                                                         r.setEnd(eNode,mark.endOffset);
494                                                         sel.addRange(r);
495                                                 }
496                                         }
497                                 }
498                         }
499                 },
500                 _changeToStep: function(from, to){
501                         // summary:
502                         //              Reverts editor to "to" setting, from the undo stack.
503                         // tags:
504                         //              private
505                         this.setValue(to.text);
506                         var b=to.bookmark;
507                         if(!b){ return; }
508                         this._moveToBookmark(b);
509                 },
510                 undo: function(){
511                         // summary:
512                         //              Handler for editor undo (ex: ctrl-z) operation
513                         // tags:
514                         //              private
515                         //console.log('undo');
516                         var ret = false;
517                         if(!this._undoRedoActive){
518                                 this._undoRedoActive = true;
519                                 this.endEditing(true);
520                                 var s=this._steps.pop();
521                                 if(s && this._steps.length>0){
522                                         this.focus();
523                                         this._changeToStep(s,this._steps[this._steps.length-1]);
524                                         this._undoedSteps.push(s);
525                                         this.onDisplayChanged();
526                                         delete this._undoRedoActive;
527                                         ret = true;
528                                 }
529                                 delete this._undoRedoActive;
530                         }
531                         return ret;
532                 },
533                 redo: function(){
534                         // summary:
535                         //              Handler for editor redo (ex: ctrl-y) operation
536                         // tags:
537                         //              private
538                         //console.log('redo');
539                         var ret = false;
540                         if(!this._undoRedoActive){
541                                 this._undoRedoActive = true;
542                                 this.endEditing(true);
543                                 var s=this._undoedSteps.pop();
544                                 if(s && this._steps.length>0){
545                                         this.focus();
546                                         this._changeToStep(this._steps[this._steps.length-1],s);
547                                         this._steps.push(s);
548                                         this.onDisplayChanged();
549                                         ret = true;
550                                 }
551                                 delete this._undoRedoActive;
552                         }
553                         return ret;
554                 },
555                 endEditing: function(ignore_caret){
556                         // summary:
557                         //              Called to note that the user has stopped typing alphanumeric characters, if it's not already noted.
558                         //              Deals with saving undo; see editActionInterval parameter.
559                         // tags:
560                         //              private
561                         if(this._editTimer){
562                                 clearTimeout(this._editTimer);
563                         }
564                         if(this._inEditing){
565                                 this._endEditing(ignore_caret);
566                                 this._inEditing=false;
567                         }
568                 },
569
570                 _getBookmark: function(){
571                         // summary:
572                         //              Get the currently selected text
573                         // tags:
574                         //              protected
575                         var b=win.withGlobal(this.window,focusBase.getBookmark);
576                         var tmp=[];
577                         if(b && b.mark){
578                                 var mark = b.mark;
579                                 if(has("ie") < 9){
580                                         // Try to use the pseudo range API on IE for better accuracy.
581                                         var sel = rangeapi.getSelection(this.window);
582                                         if(!lang.isArray(mark)){
583                                                 if(sel){
584                                                         var range;
585                                                         if(sel.rangeCount){
586                                                                 range = sel.getRangeAt(0);
587                                                         }
588                                                         if(range){
589                                                                 b.mark = range.cloneRange();
590                                                         }else{
591                                                                 b.mark = win.withGlobal(this.window,focusBase.getBookmark);
592                                                         }
593                                                 }
594                                         }else{
595                                                 // Control ranges (img, table, etc), handle differently.
596                                                 array.forEach(b.mark,function(n){
597                                                         tmp.push(rangeapi.getIndex(n,this.editNode).o);
598                                                 },this);
599                                                 b.mark = tmp;
600                                         }
601                                 }
602                                 try{
603                                         if(b.mark && b.mark.startContainer){
604                                                 tmp=rangeapi.getIndex(b.mark.startContainer,this.editNode).o;
605                                                 b.mark={startContainer:tmp,
606                                                         startOffset:b.mark.startOffset,
607                                                         endContainer:b.mark.endContainer===b.mark.startContainer?tmp:rangeapi.getIndex(b.mark.endContainer,this.editNode).o,
608                                                         endOffset:b.mark.endOffset};
609                                         }
610                                 }catch(e){
611                                         b.mark = null;
612                                 }
613                         }
614                         return b;
615                 },
616                 _beginEditing: function(){
617                         // summary:
618                         //              Called when the user starts typing alphanumeric characters.
619                         //              Deals with saving undo; see editActionInterval parameter.
620                         // tags:
621                         //              private
622                         if(this._steps.length === 0){
623                                 // You want to use the editor content without post filtering
624                                 // to make sure selection restores right for the 'initial' state.
625                                 // and undo is called.  So not using this.value, as it was 'processed'
626                                 // and the line-up for selections may have been altered.
627                                 this._steps.push({'text':html.getChildrenHtml(this.editNode),'bookmark':this._getBookmark()});
628                         }
629                 },
630                 _endEditing: function(){
631                         // summary:
632                         //              Called when the user stops typing alphanumeric characters.
633                         //              Deals with saving undo; see editActionInterval parameter.
634                         // tags:
635                         //              private
636                         // Avoid filtering to make sure selections restore.
637                         var v = html.getChildrenHtml(this.editNode);
638
639                         this._undoedSteps=[];//clear undoed steps
640                         this._steps.push({text: v, bookmark: this._getBookmark()});
641                 },
642                 onKeyDown: function(e){
643                         // summary:
644                         //              Handler for onkeydown event.
645                         // tags:
646                         //              private
647
648                         //We need to save selection if the user TAB away from this editor
649                         //no need to call _saveSelection for IE, as that will be taken care of in onBeforeDeactivate
650                         if(!has("ie") && !this.iframe && e.keyCode == keys.TAB && !this.tabIndent){
651                                 this._saveSelection();
652                         }
653                         if(!this.customUndo){
654                                 this.inherited(arguments);
655                                 return;
656                         }
657                         var k = e.keyCode;
658                         if(e.ctrlKey && !e.altKey){//undo and redo only if the special right Alt + z/y are not pressed #5892
659                                 if(k == 90 || k == 122){ //z
660                                         event.stop(e);
661                                         this.undo();
662                                         return;
663                                 }else if(k == 89 || k == 121){ //y
664                                         event.stop(e);
665                                         this.redo();
666                                         return;
667                                 }
668                         }
669                         this.inherited(arguments);
670
671                         switch(k){
672                                         case keys.ENTER:
673                                         case keys.BACKSPACE:
674                                         case keys.DELETE:
675                                                 this.beginEditing();
676                                                 break;
677                                         case 88: //x
678                                         case 86: //v
679                                                 if(e.ctrlKey && !e.altKey && !e.metaKey){
680                                                         this.endEditing();//end current typing step if any
681                                                         if(e.keyCode == 88){
682                                                                 this.beginEditing('cut');
683                                                                 //use timeout to trigger after the cut is complete
684                                                                 setTimeout(lang.hitch(this, this.endEditing), 1);
685                                                         }else{
686                                                                 this.beginEditing('paste');
687                                                                 //use timeout to trigger after the paste is complete
688                                                                 setTimeout(lang.hitch(this, this.endEditing), 1);
689                                                         }
690                                                         break;
691                                                 }
692                                                 //pass through
693                                         default:
694                                                 if(!e.ctrlKey && !e.altKey && !e.metaKey && (e.keyCode<keys.F1 || e.keyCode>keys.F15)){
695                                                         this.beginEditing();
696                                                         break;
697                                                 }
698                                                 //pass through
699                                         case keys.ALT:
700                                                 this.endEditing();
701                                                 break;
702                                         case keys.UP_ARROW:
703                                         case keys.DOWN_ARROW:
704                                         case keys.LEFT_ARROW:
705                                         case keys.RIGHT_ARROW:
706                                         case keys.HOME:
707                                         case keys.END:
708                                         case keys.PAGE_UP:
709                                         case keys.PAGE_DOWN:
710                                                 this.endEditing(true);
711                                                 break;
712                                         //maybe ctrl+backspace/delete, so don't endEditing when ctrl is pressed
713                                         case keys.CTRL:
714                                         case keys.SHIFT:
715                                         case keys.TAB:
716                                                 break;
717                                 }
718                 },
719                 _onBlur: function(){
720                         // summary:
721                         //              Called from focus manager when focus has moved away from this editor
722                         // tags:
723                         //              protected
724
725                         //this._saveSelection();
726                         this.inherited(arguments);
727                         this.endEditing(true);
728                 },
729                 _saveSelection: function(){
730                         // summary:
731                         //              Save the currently selected text in _savedSelection attribute
732                         // tags:
733                         //              private
734                         try{
735                                 this._savedSelection=this._getBookmark();
736                         }catch(e){ /* Squelch any errors that occur if selection save occurs due to being hidden simultaneously. */}
737                 },
738                 _restoreSelection: function(){
739                         // summary:
740                         //              Re-select the text specified in _savedSelection attribute;
741                         //              see _saveSelection().
742                         // tags:
743                         //              private
744                         if(this._savedSelection){
745                                 // Clear off cursor to start, we're deliberately going to a selection.
746                                 delete this._cursorToStart;
747                                 // only restore the selection if the current range is collapsed
748                                 // if not collapsed, then it means the editor does not lose
749                                 // selection and there is no need to restore it
750                                 if(win.withGlobal(this.window,'isCollapsed',dijit)){
751                                         this._moveToBookmark(this._savedSelection);
752                                 }
753                                 delete this._savedSelection;
754                         }
755                 },
756
757                 onClick: function(){
758                         // summary:
759                         //              Handler for when editor is clicked
760                         // tags:
761                         //              protected
762                         this.endEditing(true);
763                         this.inherited(arguments);
764                 },
765
766                 replaceValue: function(/*String*/ html){
767                         // summary:
768                         //              over-ride of replaceValue to support custom undo and stack maintenance.
769                         // tags:
770                         //              protected
771                         if(!this.customUndo){
772                                 this.inherited(arguments);
773                         }else{
774                                 if(this.isClosed){
775                                         this.setValue(html);
776                                 }else{
777                                         this.beginEditing();
778                                         if(!html){
779                                                 html = "&#160;";        // &nbsp;
780                                         }
781                                         this.setValue(html);
782                                         this.endEditing();
783                                 }
784                         }
785                 },
786
787                 _setDisabledAttr: function(/*Boolean*/ value){
788                         var disableFunc = lang.hitch(this, function(){
789                                 if((!this.disabled && value) || (!this._buttonEnabledPlugins && value)){
790                                 // Disable editor: disable all enabled buttons and remember that list
791                                         array.forEach(this._plugins, function(p){
792                                                 p.set("disabled", true);
793                                 });
794                         }else if(this.disabled && !value){
795                                         // Restore plugins to being active.
796                                         array.forEach(this._plugins, function(p){
797                                                 p.set("disabled", false);
798                                 });
799                         }
800                         });
801                         this.setValueDeferred.addCallback(disableFunc);
802                         this.inherited(arguments);
803                 },
804
805                 _setStateClass: function(){
806                         try{
807                                 this.inherited(arguments);
808
809                                 // Let theme set the editor's text color based on editor enabled/disabled state.
810                                 // We need to jump through hoops because the main document (where the theme CSS is)
811                                 // is separate from the iframe's document.
812                                 if(this.document && this.document.body){
813                                         domStyle.set(this.document.body, "color", domStyle.get(this.iframe, "color"));
814                                 }
815                         }catch(e){ /* Squelch any errors caused by focus change if hidden during a state change */}
816                 }
817         });
818
819         // Register the "default plugins", ie, the built-in editor commands
820         function simplePluginFactory(args){
821                 return new _Plugin({ command: args.name });
822         }
823         function togglePluginFactory(args){
824                 return new _Plugin({ buttonClass: ToggleButton, command: args.name });
825         }
826         lang.mixin(_Plugin.registry, {
827                 "undo": simplePluginFactory,
828                 "redo": simplePluginFactory,
829                 "cut": simplePluginFactory,
830                 "copy": simplePluginFactory,
831                 "paste": simplePluginFactory,
832                 "insertOrderedList": simplePluginFactory,
833                 "insertUnorderedList": simplePluginFactory,
834                 "indent": simplePluginFactory,
835                 "outdent": simplePluginFactory,
836                 "justifyCenter": simplePluginFactory,
837                 "justifyFull": simplePluginFactory,
838                 "justifyLeft": simplePluginFactory,
839                 "justifyRight": simplePluginFactory,
840                 "delete": simplePluginFactory,
841                 "selectAll": simplePluginFactory,
842                 "removeFormat": simplePluginFactory,
843                 "unlink": simplePluginFactory,
844                 "insertHorizontalRule": simplePluginFactory,
845
846                 "bold": togglePluginFactory,
847                 "italic": togglePluginFactory,
848                 "underline": togglePluginFactory,
849                 "strikethrough": togglePluginFactory,
850                 "subscript": togglePluginFactory,
851                 "superscript": togglePluginFactory,
852
853                 "|": function(){
854                         return new _Plugin({ button: new ToolbarSeparator(), setEditor: function(editor){this.editor = editor;}});
855                 }
856         });
857
858         return Editor;
859 });