1
dojo.provide("dijit.Editor");
2
dojo.require("dijit._editor.RichText");
3
dojo.require("dijit.Toolbar");
4
dojo.require("dijit.ToolbarSeparator");
5
dojo.require("dijit._editor._Plugin");
6
dojo.require("dijit._editor.plugins.EnterKeyHandling");
7
dojo.require("dijit._editor.range");
8
dojo.require("dijit._Container");
9
dojo.require("dojo.i18n");
10
dojo.requireLocalization("dijit._editor", "commands");
14
dijit._editor.RichText,
17
// A rich text Editing widget
20
// This widget provides basic WYSIWYG editing features, based on the browser's
21
// underlying rich text editing capability, accompanied by a toolbar (dijit.Toolbar).
22
// A plugin model is available to extend the editor's capabilities as well as the
23
// the options available in the toolbar. Content generation may vary across
24
// browsers, and clipboard operations may have different results, to name
25
// a few limitations. Note: this widget should not be used with the HTML
26
// <TEXTAREA> tag -- see dijit._editor.RichText for details.
29
// A list of plugin names (as strings) or instances (as objects)
33
// extraPlugins: String[]
34
// A list of extra plugin names which will be appended to plugins array
37
constructor: function(){
39
// Runs on widget initialization to setup arrays etc.
43
if(!dojo.isArray(this.plugins)){
44
this.plugins=["undo","redo","|","cut","copy","paste","|","bold","italic","underline","strikethrough","|",
45
"insertOrderedList","insertUnorderedList","indent","outdent","|","justifyLeft","justifyRight","justifyCenter","justifyFull",
46
"dijit._editor.plugins.EnterKeyHandling" /*, "createLink"*/];
50
this._editInterval = this.editActionInterval * 1000;
52
//IE will always lose focus when other element gets focus, while for FF and safari,
53
//when no iframe is used, focus will be lost whenever another element gets focus.
54
//For IE, we can connect to onBeforeDeactivate, which will be called right before
55
//the focus is lost, so we can obtain the selected range. For other browsers,
56
//no equivelent of onBeforeDeactivate, so we need to do two things to make sure
57
//selection is properly saved before focus is lost: 1) when user clicks another
58
//element in the page, in which case we listen to mousedown on the entire page and
59
//see whether user clicks out of a focus editor, if so, save selection (focus will
60
//only lost after onmousedown event is fired, so we can obtain correct caret pos.)
61
//2) when user tabs away from the editor, which is handled in onKeyDown below.
63
this.events.push("onBeforeDeactivate");
67
postCreate: function(){
68
//for custom undo/redo
70
dojo['require']("dijit._editor.range");
71
this._steps=this._steps.slice(0);
72
this._undoedSteps=this._undoedSteps.slice(0);
73
// this.addKeyHandler('z',this.KEY_CTRL,this.undo);
74
// this.addKeyHandler('y',this.KEY_CTRL,this.redo);
76
if(dojo.isArray(this.extraPlugins)){
77
this.plugins=this.plugins.concat(this.extraPlugins);
81
this.inherited(arguments);
82
// dijit.Editor.superclass.postCreate.apply(this, arguments);
84
this.commands = dojo.i18n.getLocalization("dijit._editor", "commands", this.lang);
87
// if we haven't been assigned a toolbar, create one
88
this.toolbar = new dijit.Toolbar({});
89
dojo.place(this.toolbar.domNode, this.editingArea, "before");
92
dojo.forEach(this.plugins, this.addPlugin, this);
93
this.onNormalizedDisplayChanged(); //update toolbar button status
94
// }catch(e){ console.debug(e); }
96
this.toolbar.startup();
99
dojo.forEach(this._plugins, function(p){
105
this.toolbar.destroyRecursive();
107
this.inherited(arguments);
109
addPlugin: function(/*String||Object*/plugin, /*Integer?*/index){
111
// takes a plugin name as a string or a plugin instance and
112
// adds it to the toolbar and associates it with this editor
113
// instance. The resulting plugin is added to the Editor's
114
// plugins array. If index is passed, it's placed in the plugins
115
// array at that index. No big magic, but a nice helper for
116
// passing in plugin names via markup.
118
// plugin: String, args object or plugin instance
121
// This object will be passed to the plugin constructor
124
// Used when creating an instance from
125
// something already in this.plugins. Ensures that the new
126
// instance is assigned to this.plugins at that index.
127
var args=dojo.isString(plugin)?{name:plugin}:plugin;
129
var o={"args":args,"plugin":null,"editor":this};
130
dojo.publish(dijit._scopeName + ".Editor.getPlugin",[o]);
132
var pc = dojo.getObject(args.name);
134
o.plugin=new pc(args);
138
console.warn('Cannot find plugin',plugin);
143
if(arguments.length > 1){
144
this._plugins[index] = plugin;
146
this._plugins.push(plugin);
148
plugin.setEditor(this);
149
if(dojo.isFunction(plugin.setToolbar)){
150
plugin.setToolbar(this.toolbar);
153
//the following 3 functions are required to make the editor play nice under a layout widget, see #4070
156
// Exists to make Editor work as a child of a layout widget.
157
// Developers don't need to call this method.
160
//console.log('startup',arguments);
162
resize: function(size){
164
// Resize the editor to the specified size, see `dijit.layout._LayoutWidget.resize`
165
dijit.layout._LayoutWidget.prototype.resize.apply(this,arguments);
169
// Called from `dijit.layout._LayoutWidget.resize`. This shouldn't be called directly
172
this.editingArea.style.height=(this._contentBox.h - dojo.marginBox(this.toolbar.domNode).h)+"px";
174
this.iframe.style.height="100%";
176
this._layoutMode = true;
178
_onIEMouseDown: function(/*Event*/ e){
180
// IE only to prevent 2 clicks to focus
183
delete this._savedSelection; // new mouse position overrides old selection
184
if(e.target.tagName == "BODY"){
185
setTimeout(dojo.hitch(this, "placeCursorAtEnd"), 0);
187
this.inherited(arguments);
189
onBeforeDeactivate: function(e){
191
// Called on IE right before focus is lost. Saves the selected range.
195
this.endEditing(true);
197
//in IE, the selection will be lost when other elements get focus,
198
//let's save focus before the editor is deactivated
199
this._saveSelection();
200
//console.log('onBeforeDeactivate',this);
203
/* beginning of custom undo/redo support */
205
// customUndo: Boolean
206
// Whether we shall use custom undo/redo support instead of the native
207
// browser support. By default, we only enable customUndo for IE, as it
208
// has broken native undo/redo support. Note: the implementation does
209
// support other browsers which have W3C DOM2 Range API implemented.
210
customUndo: dojo.isIE,
212
// editActionInterval: Integer
213
// When using customUndo, not every keystroke will be saved as a step.
214
// Instead typing (including delete) will be grouped together: after
215
// a user stops typing for editActionInterval seconds, a step will be
216
// saved; if a user resume typing within editActionInterval seconds,
217
// the timeout will be restarted. By default, editActionInterval is 3
219
editActionInterval: 3,
221
beginEditing: function(cmd){
223
// Called to note that the user has started typing alphanumeric characters, if it's not already noted.
224
// Deals with saving undo; see editActionInterval parameter.
227
if(!this._inEditing){
228
this._inEditing=true;
229
this._beginEditing(cmd);
231
if(this.editActionInterval>0){
233
clearTimeout(this._editTimer);
235
this._editTimer = setTimeout(dojo.hitch(this, this.endEditing), this._editInterval);
240
execCommand: function(cmd){
242
// Main handler for executing any commands to the editor, like paste, bold, etc.
243
// Called by plugins, but not meant to be called by end users.
246
if(this.customUndo && (cmd=='undo' || cmd=='redo')){
251
this._beginEditing();
254
var r = this.inherited('execCommand', arguments);
255
if(dojo.isWebKit && cmd=='paste' && !r){ //see #4598: safari does not support invoking paste from js
256
throw { code: 1011 }; // throw an object like Mozilla's error
259
//TODO: when else might we get an exception? Do we need the Mozilla test below?
260
if(e.code == 1011 /* Mozilla: service denied */ && /copy|cut|paste/.test(cmd)){
261
// Warn user of platform limitation. Cannot programmatically access clipboard. See ticket #4136
262
var sub = dojo.string.substitute,
263
accel = {cut:'X', copy:'C', paste:'V'},
264
isMac = navigator.userAgent.indexOf("Macintosh") != -1;
265
alert(sub(this.commands.systemShortcut,
266
[this.commands[cmd], sub(this.commands[isMac ? 'appleKey' : 'ctrlKey'], [accel[cmd]])]));
276
queryCommandEnabled: function(cmd){
278
// Returns true if specified editor command is enabled.
279
// Used by the plugins to know when to highlight/not highlight buttons.
282
if(this.customUndo && (cmd=='undo' || cmd=='redo')){
283
return cmd=='undo'?(this._steps.length>1):(this._undoedSteps.length>0);
285
return this.inherited('queryCommandEnabled',arguments);
291
// Set focus inside the editor
293
//console.log('focus',dijit._curFocus==this.editNode)
294
if(this._savedSelection && dojo.isIE){
295
restore = dijit._curFocus!=this.editNode;
297
this.inherited(arguments);
299
this._restoreSelection();
302
_moveToBookmark: function(b){
304
// Selects the text specified in bookmark b
309
if(dojo.isArray(b)){//IE CONTROL
311
dojo.forEach(b,function(n){
312
bookmark.push(dijit.range.getNode(n,this.editNode));
316
var r=dijit.range.create();
317
r.setStart(dijit.range.getNode(b.startContainer,this.editNode),b.startOffset);
318
r.setEnd(dijit.range.getNode(b.endContainer,this.editNode),b.endOffset);
321
dojo.withGlobal(this.window,'moveToBookmark',dijit,[bookmark]);
323
_changeToStep: function(from, to){
325
// Reverts editor to "to" setting, from the undo stack.
328
this.setValue(to.text);
331
this._moveToBookmark(b);
335
// Handler for editor undo (ex: ctrl-z) operation
338
// console.log('undo');
339
this.endEditing(true);
340
var s=this._steps.pop();
341
if(this._steps.length>0){
343
this._changeToStep(s,this._steps[this._steps.length-1]);
344
this._undoedSteps.push(s);
345
this.onDisplayChanged();
352
// Handler for editor redo (ex: ctrl-y) operation
356
// console.log('redo');
357
this.endEditing(true);
358
var s=this._undoedSteps.pop();
359
if(s && this._steps.length>0){
361
this._changeToStep(this._steps[this._steps.length-1],s);
363
this.onDisplayChanged();
368
endEditing: function(ignore_caret){
370
// Called to note that the user has stopped typing alphanumeric characters, if it's not already noted.
371
// Deals with saving undo; see editActionInterval parameter.
375
clearTimeout(this._editTimer);
378
this._endEditing(ignore_caret);
379
this._inEditing=false;
382
_getBookmark: function(){
384
// Get the currently selected text
387
var b=dojo.withGlobal(this.window,dijit.getBookmark);
390
if(dojo.isArray(b)){//CONTROL
391
dojo.forEach(b,function(n){
392
tmp.push(dijit.range.getIndex(n,this.editNode).o);
397
tmp=dijit.range.getIndex(b.startContainer,this.editNode).o;
398
b={startContainer:tmp,
399
startOffset:b.startOffset,
400
endContainer:b.endContainer===b.startContainer?tmp:dijit.range.getIndex(b.endContainer,this.editNode).o,
401
endOffset:b.endOffset};
405
_beginEditing: function(cmd){
407
// Called when the user starts typing alphanumeric characters.
408
// Deals with saving undo; see editActionInterval parameter.
411
if(this._steps.length===0){
412
this._steps.push({'text':this.savedContent,'bookmark':this._getBookmark()});
415
_endEditing: function(ignore_caret){
417
// Called when the user stops typing alphanumeric characters.
418
// Deals with saving undo; see editActionInterval parameter.
421
var v=this.getValue(true);
423
this._undoedSteps=[];//clear undoed steps
424
this._steps.push({text: v, bookmark: this._getBookmark()});
426
onKeyDown: function(e){
428
// Handler for onkeydown event.
432
//We need to save selection if the user TAB away from this editor
433
//no need to call _saveSelection for IE, as that will be taken care of in onBeforeDeactivate
434
if(!dojo.isIE && !this.iframe && e.keyCode==dojo.keys.TAB && !this.tabIndent){
435
this._saveSelection();
437
if(!this.customUndo){
438
this.inherited(arguments);
441
var k = e.keyCode, ks = dojo.keys;
442
if(e.ctrlKey && !e.altKey){//undo and redo only if the special right Alt + z/y are not pressed #5892
443
if(k == 90 || k == 122){ //z
447
}else if(k == 89 || k == 121){ //y
453
this.inherited(arguments);
463
if(e.ctrlKey && !e.altKey && !e.metaKey){
464
this.endEditing();//end current typing step if any
466
this.beginEditing('cut');
467
//use timeout to trigger after the cut is complete
468
setTimeout(dojo.hitch(this, this.endEditing), 1);
470
this.beginEditing('paste');
471
//use timeout to trigger after the paste is complete
472
setTimeout(dojo.hitch(this, this.endEditing), 1);
478
if(!e.ctrlKey && !e.altKey && !e.metaKey && (e.keyCode<dojo.keys.F1 || e.keyCode>dojo.keys.F15)){
494
this.endEditing(true);
496
//maybe ctrl+backspace/delete, so don't endEditing when ctrl is pressed
505
// Called from focus manager when focus has moved away from this editor
509
//this._saveSelection();
510
this.inherited('_onBlur',arguments);
511
this.endEditing(true);
513
_saveSelection: function(){
515
// Save the currently selected text in _savedSelection attribute
518
this._savedSelection=this._getBookmark();
519
//console.log('save selection',this._savedSelection,this);
521
_restoreSelection: function(){
523
// Re-select the text specified in _savedSelection attribute;
524
// see _saveSelection().
527
if(this._savedSelection){
528
//only restore the selection if the current range is collapsed
529
//if not collapsed, then it means the editor does not lose
530
//selection and there is no need to restore it
531
//if(dojo.withGlobal(this.window,'isCollapsed',dijit)){
532
//console.log('_restoreSelection true')
533
this._moveToBookmark(this._savedSelection);
535
delete this._savedSelection;
538
_onFocus: function(){
540
// Called from focus manager when focus has moved into this editor
544
//console.log('_onFocus');
545
setTimeout(dojo.hitch(this, "_restoreSelection"), 0); // needs input caret first
546
this.inherited(arguments);
551
// Handler for when editor is clicked
554
this.endEditing(true);
555
this.inherited(arguments);
557
/* end of custom undo/redo support */
561
// Register the "default plugins", ie, the built-in editor commands
562
dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){
563
if(o.plugin){ return; }
564
var args = o.args, p;
565
var _p = dijit._editor._Plugin;
566
var name = args.name;
568
case "undo": case "redo": case "cut": case "copy": case "paste": case "insertOrderedList":
569
case "insertUnorderedList": case "indent": case "outdent": case "justifyCenter":
570
case "justifyFull": case "justifyLeft": case "justifyRight": case "delete":
571
case "selectAll": case "removeFormat": case "unlink":
572
case "insertHorizontalRule":
573
p = new _p({ command: name });
576
case "bold": case "italic": case "underline": case "strikethrough":
577
case "subscript": case "superscript":
578
p = new _p({ buttonClass: dijit.form.ToggleButton, command: name });
581
p = new _p({ button: new dijit.ToolbarSeparator() });
583
// console.log('name',name,p);
b'\\ No newline at end of file'