source: Dev/trunk/src/client/dijit/Editor.js @ 493

Last change on this file since 493 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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