]> git.wh0rd.org - tt-rss.git/blame - lib/dijit/Editor.js.uncompressed.js
modify dojo rebuild script to remove uncompressed files
[tt-rss.git] / lib / dijit / Editor.js.uncompressed.js
CommitLineData
f0cfe83e
AD
1define("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});