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