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