dijit
Version:
Dijit provides a complete collection of user interface controls based on Dojo, giving you the power to create web applications that are highly optimized for usability, performance, internationalization, accessibility, but above all deliver an incredible u
877 lines (820 loc) • 28.5 kB
JavaScript
define([
"require",
"dojo/_base/array", // array.forEach
"dojo/_base/declare", // declare
"dojo/Deferred", // Deferred
"dojo/i18n", // i18n.getLocalization
"dojo/dom-attr", // domAttr.set
"dojo/dom-class", // domClass.add
"dojo/dom-geometry",
"dojo/dom-style", // domStyle.set, get
"dojo/keys", // keys.F1 keys.F15 keys.TAB
"dojo/_base/lang", // lang.getObject lang.hitch
"dojo/sniff", // has("ie") has("mac") has("webkit")
"dojo/string", // string.substitute
"dojo/topic", // topic.publish()
"./_Container",
"./Toolbar",
"./ToolbarSeparator",
"./layout/_LayoutWidget",
"./form/ToggleButton",
"./_editor/_Plugin",
"./_editor/plugins/EnterKeyHandling",
"./_editor/html",
"./_editor/range",
"./_editor/RichText",
"./main", // dijit._scopeName
"dojo/i18n!./_editor/nls/commands"
], function(require, array, declare, Deferred, i18n, domAttr, domClass, domGeometry, domStyle,
keys, lang, has, string, topic,
_Container, Toolbar, ToolbarSeparator, _LayoutWidget, ToggleButton,
_Plugin, EnterKeyHandling, html, rangeapi, RichText, dijit){
// module:
// dijit/Editor
var Editor = declare("dijit.Editor", RichText, {
// summary:
// A rich text Editing widget
//
// description:
// This widget provides basic WYSIWYG editing features, based on the browser's
// underlying rich text editing capability, accompanied by a toolbar (`dijit.Toolbar`).
// A plugin model is available to extend the editor's capabilities as well as the
// the options available in the toolbar. Content generation may vary across
// browsers, and clipboard operations may have different results, to name
// a few limitations. Note: this widget should not be used with the HTML
// <TEXTAREA> tag -- see dijit/_editor/RichText for details.
// plugins: [const] Object[]
// A list of plugin names (as strings) or instances (as objects)
// for this widget.
//
// When declared in markup, it might look like:
// | plugins="['bold',{name:'dijit._editor.plugins.FontChoice', command:'fontName', generic:true}]"
plugins: null,
// extraPlugins: [const] Object[]
// A list of extra plugin names which will be appended to plugins array
extraPlugins: null,
constructor: function(/*===== params, srcNodeRef =====*/){
// summary:
// Create the widget.
// params: Object|null
// Initial settings for any of the attributes, except readonly attributes.
// srcNodeRef: DOMNode
// The editor replaces the specified DOMNode.
if(!lang.isArray(this.plugins)){
this.plugins = ["undo", "redo", "|", "cut", "copy", "paste", "|", "bold", "italic", "underline", "strikethrough", "|",
"insertOrderedList", "insertUnorderedList", "indent", "outdent", "|", "justifyLeft", "justifyRight", "justifyCenter", "justifyFull",
EnterKeyHandling /*, "createLink"*/];
}
this._plugins = [];
this._editInterval = this.editActionInterval * 1000;
//IE will always lose focus when other element gets focus, while for FF and safari,
//when no iframe is used, focus will be lost whenever another element gets focus.
//For IE, we can connect to onBeforeDeactivate, which will be called right before
//the focus is lost, so we can obtain the selected range. For other browsers,
//no equivalent of onBeforeDeactivate, so we need to do two things to make sure
//selection is properly saved before focus is lost: 1) when user clicks another
//element in the page, in which case we listen to mousedown on the entire page and
//see whether user clicks out of a focus editor, if so, save selection (focus will
//only lost after onmousedown event is fired, so we can obtain correct caret pos.)
//2) when user tabs away from the editor, which is handled in onKeyDown below.
if(has("ie") || has("trident") || has("edge")){
this.events.push("onBeforeDeactivate");
this.events.push("onBeforeActivate");
}
},
postMixInProperties: function(){
// summary:
// Extension to make sure a deferred is in place before certain functions
// execute, like making sure all the plugins are properly inserted.
// Set up a deferred so that the value isn't applied to the editor
// until all the plugins load, needed to avoid timing condition
// reported in #10537.
this.setValueDeferred = new Deferred();
this.inherited(arguments);
},
postCreate: function(){
this.inherited(arguments);
//for custom undo/redo, if enabled.
this._steps = this._steps.slice(0);
this._undoedSteps = this._undoedSteps.slice(0);
if(lang.isArray(this.extraPlugins)){
this.plugins = this.plugins.concat(this.extraPlugins);
}
this.commands = i18n.getLocalization("dijit._editor", "commands", this.lang);
if(has("webkit")){
// Disable selecting the entire editor by inadvertent double-clicks.
// on buttons, title bar, etc. Otherwise clicking too fast on
// a button such as undo/redo selects the entire editor.
domStyle.set(this.domNode, "KhtmlUserSelect", "none");
}
},
startup: function(){
this.inherited(arguments);
if(!this.toolbar){
// if we haven't been assigned a toolbar, create one
this.toolbar = new Toolbar({
ownerDocument: this.ownerDocument,
dir: this.dir,
lang: this.lang,
"aria-label": this.id
});
this.header.appendChild(this.toolbar.domNode);
}
array.forEach(this.plugins, this.addPlugin, this);
// Okay, denote the value can now be set.
this.setValueDeferred.resolve(true);
domClass.add(this.iframe.parentNode, "dijitEditorIFrameContainer");
domClass.add(this.iframe, "dijitEditorIFrame");
domAttr.set(this.iframe, "allowTransparency", true);
this.toolbar.startup();
this.onNormalizedDisplayChanged(); //update toolbar button status
},
destroy: function(){
array.forEach(this._plugins, function(p){
if(p && p.destroy){
p.destroy();
}
});
this._plugins = [];
this.toolbar.destroyRecursive();
delete this.toolbar;
this.inherited(arguments);
},
addPlugin: function(/*String||Object||Function*/ plugin, /*Integer?*/ index){
// summary:
// takes a plugin name as a string or a plugin instance and
// adds it to the toolbar and associates it with this editor
// instance. The resulting plugin is added to the Editor's
// plugins array. If index is passed, it's placed in the plugins
// array at that index. No big magic, but a nice helper for
// passing in plugin names via markup.
// plugin:
// String, args object, plugin instance, or plugin constructor
// args:
// This object will be passed to the plugin constructor
// index:
// Used when creating an instance from
// something already in this.plugins. Ensures that the new
// instance is assigned to this.plugins at that index.
var args = lang.isString(plugin) ? {name: plugin} : lang.isFunction(plugin) ? {ctor: plugin} : plugin;
if(!args.setEditor){
var o = {"args": args, "plugin": null, "editor": this};
if(args.name){
// search registry for a plugin factory matching args.name, if it's not there then
// fallback to 1.0 API:
// ask all loaded plugin modules to fill in o.plugin if they can (ie, if they implement args.name)
// remove fallback for 2.0.
if(_Plugin.registry[args.name]){
o.plugin = _Plugin.registry[args.name](args);
}else{
topic.publish(dijit._scopeName + ".Editor.getPlugin", o); // publish
}
}
if(!o.plugin){
try{
// TODO: remove lang.getObject() call in 2.0
var pc = args.ctor || lang.getObject(args.name) || require(args.name);
if(pc){
o.plugin = new pc(args);
}
}catch(e){
throw new Error(this.id + ": cannot find plugin [" + args.name + "]");
}
}
if(!o.plugin){
throw new Error(this.id + ": cannot find plugin [" + args.name + "]");
}
plugin = o.plugin;
}
if(arguments.length > 1){
this._plugins[index] = plugin;
}else{
this._plugins.push(plugin);
}
plugin.setEditor(this);
if(lang.isFunction(plugin.setToolbar)){
plugin.setToolbar(this.toolbar);
}
},
//the following 2 functions are required to make the editor play nice under a layout widget, see #4070
resize: function(size){
// summary:
// Resize the editor to the specified size, see `dijit/layout/_LayoutWidget.resize()`
if(size){
// we've been given a height/width for the entire editor (toolbar + contents), calls layout()
// to split the allocated size between the toolbar and the contents
_LayoutWidget.prototype.resize.apply(this, arguments);
}
/*
else{
// do nothing, the editor is already laid out correctly. The user has probably specified
// the height parameter, which was used to set a size on the iframe
}
*/
},
layout: function(){
// summary:
// Called from `dijit/layout/_LayoutWidget.resize()`. This shouldn't be called directly
// tags:
// protected
// Converts the iframe (or rather the <div> surrounding it) to take all the available space
// except what's needed for the header (toolbars) and footer (breadcrumbs, etc).
// A class was added to the iframe container and some themes style it, so we have to
// calc off the added margins and padding too. See tracker: #10662
var areaHeight = (this._contentBox.h -
(this.getHeaderHeight() + this.getFooterHeight() +
domGeometry.getPadBorderExtents(this.iframe.parentNode).h +
domGeometry.getMarginExtents(this.iframe.parentNode).h));
this.editingArea.style.height = areaHeight + "px";
if(this.iframe){
this.iframe.style.height = "100%";
}
this._layoutMode = true;
},
_onIEMouseDown: function(/*Event*/ e){
// summary:
// IE only to prevent 2 clicks to focus
// tags:
// private
var outsideClientArea;
// IE 8's componentFromPoint is broken, which is a shame since it
// was smaller code, but oh well. We have to do this brute force
// to detect if the click was scroller or not.
var b = this.document.body;
var clientWidth = b.clientWidth;
var clientHeight = b.clientHeight;
var clientLeft = b.clientLeft;
var offsetWidth = b.offsetWidth;
var offsetHeight = b.offsetHeight;
var offsetLeft = b.offsetLeft;
//Check for vertical scroller click.
if(/^rtl$/i.test(b.dir || "")){
if(clientWidth < offsetWidth && e.x > clientWidth && e.x < offsetWidth){
// Check the click was between width and offset width, if so, scroller
outsideClientArea = true;
}
}else{
// RTL mode, we have to go by the left offsets.
if(e.x < clientLeft && e.x > offsetLeft){
// Check the click was between width and offset width, if so, scroller
outsideClientArea = true;
}
}
if(!outsideClientArea){
// Okay, might be horiz scroller, check that.
if(clientHeight < offsetHeight && e.y > clientHeight && e.y < offsetHeight){
// Horizontal scroller.
outsideClientArea = true;
}
}
if(!outsideClientArea){
delete this._cursorToStart; // Remove the force to cursor to start position.
delete this._savedSelection; // new mouse position overrides old selection
if(e.target.tagName == "BODY"){
this.defer("placeCursorAtEnd");
}
this.inherited(arguments);
}
},
onBeforeActivate: function(){
this._restoreSelection();
},
onBeforeDeactivate: function(e){
// summary:
// Called on IE right before focus is lost. Saves the selected range.
// tags:
// private
if(this.customUndo){
this.endEditing(true);
}
//in IE, the selection will be lost when other elements get focus,
//let's save focus before the editor is deactivated
if(e.target.tagName != "BODY"){
this._saveSelection();
}
//console.log('onBeforeDeactivate',this);
},
/* beginning of custom undo/redo support */
// customUndo: Boolean
// Whether we shall use custom undo/redo support instead of the native
// browser support. By default, we now use custom undo. It works better
// than native browser support and provides a consistent behavior across
// browsers with a minimal performance hit. We already had the hit on
// the slowest browser, IE, anyway.
customUndo: true,
// editActionInterval: Integer
// When using customUndo, not every keystroke will be saved as a step.
// Instead typing (including delete) will be grouped together: after
// a user stops typing for editActionInterval seconds, a step will be
// saved; if a user resume typing within editActionInterval seconds,
// the timeout will be restarted. By default, editActionInterval is 3
// seconds.
editActionInterval: 3,
beginEditing: function(cmd){
// summary:
// Called to note that the user has started typing alphanumeric characters, if it's not already noted.
// Deals with saving undo; see editActionInterval parameter.
// tags:
// private
if(!this._inEditing){
this._inEditing = true;
this._beginEditing(cmd);
}
if(this.editActionInterval > 0){
if(this._editTimer){
this._editTimer.remove();
}
this._editTimer = this.defer("endEditing", this._editInterval);
}
},
// TODO: declaring these in the prototype is meaningless, just create in the constructor/postCreate
_steps: [],
_undoedSteps: [],
execCommand: function(cmd){
// summary:
// Main handler for executing any commands to the editor, like paste, bold, etc.
// Called by plugins, but not meant to be called by end users.
// tags:
// protected
if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){
return this[cmd]();
}else{
if(this.customUndo){
this.endEditing();
this._beginEditing();
}
var r = this.inherited(arguments);
if(this.customUndo){
this._endEditing();
}
return r;
}
},
_pasteImpl: function(){
// summary:
// Over-ride of paste command control to make execCommand cleaner
// tags:
// Protected
return this._clipboardCommand("paste");
},
_cutImpl: function(){
// summary:
// Over-ride of cut command control to make execCommand cleaner
// tags:
// Protected
return this._clipboardCommand("cut");
},
_copyImpl: function(){
// summary:
// Over-ride of copy command control to make execCommand cleaner
// tags:
// Protected
return this._clipboardCommand("copy");
},
_clipboardCommand: function(cmd){
// summary:
// Function to handle processing clipboard commands (or at least try to).
// tags:
// Private
var r;
try{
// Try to exec the superclass exec-command and see if it works.
r = this.document.execCommand(cmd, false, null);
if(has("webkit") && !r){ //see #4598: webkit does not guarantee clipboard support from js
throw {}; // throw to show the warning
}
}catch(e){
//Ticket #18467 removed the checks to specific codes
// Warn user of platform limitation. Cannot programmatically access clipboard. See ticket #4136
var sub = string.substitute,
accel = {cut: 'X', copy: 'C', paste: 'V'};
alert(sub(this.commands.systemShortcut,
[this.commands[cmd], sub(this.commands[has("mac") ? 'appleKey' : 'ctrlKey'], [accel[cmd]])]));
r = false;
}
return r;
},
queryCommandEnabled: function(cmd){
// summary:
// Returns true if specified editor command is enabled.
// Used by the plugins to know when to highlight/not highlight buttons.
// tags:
// protected
if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){
return cmd == 'undo' ? (this._steps.length > 1) : (this._undoedSteps.length > 0);
}else{
return this.inherited(arguments);
}
},
_moveToBookmark: function(b){
// summary:
// Selects the text specified in bookmark b
// tags:
// private
var bookmark = b.mark;
var mark = b.mark;
var col = b.isCollapsed;
var r, sNode, eNode, sel;
if(mark){
if(has("ie") < 9 || (has("ie") === 9 && has("quirks"))){
if(lang.isArray(mark)){
// IE CONTROL, have to use the native bookmark.
bookmark = [];
array.forEach(mark, function(n){
bookmark.push(rangeapi.getNode(n, this.editNode));
}, this);
this.selection.moveToBookmark({mark: bookmark, isCollapsed: col});
}else{
if(mark.startContainer && mark.endContainer){
// Use the pseudo WC3 range API. This works better for positions
// than the IE native bookmark code.
sel = rangeapi.getSelection(this.window);
if(sel && sel.removeAllRanges){
sel.removeAllRanges();
r = rangeapi.create(this.window);
sNode = rangeapi.getNode(mark.startContainer, this.editNode);
eNode = rangeapi.getNode(mark.endContainer, this.editNode);
if(sNode && eNode){
// Okay, we believe we found the position, so add it into the selection
// There are cases where it may not be found, particularly in undo/redo, when
// IE changes the underlying DOM on us (wraps text in a <p> tag or similar.
// So, in those cases, don't bother restoring selection.
r.setStart(sNode, mark.startOffset);
r.setEnd(eNode, mark.endOffset);
sel.addRange(r);
}
}
}
}
}else{//w3c range
sel = rangeapi.getSelection(this.window);
if(sel && sel.removeAllRanges){
sel.removeAllRanges();
r = rangeapi.create(this.window);
sNode = rangeapi.getNode(mark.startContainer, this.editNode);
eNode = rangeapi.getNode(mark.endContainer, this.editNode);
if(sNode && eNode){
// Okay, we believe we found the position, so add it into the selection
// There are cases where it may not be found, particularly in undo/redo, when
// formatting as been done and so on, so don't restore selection then.
r.setStart(sNode, mark.startOffset);
r.setEnd(eNode, mark.endOffset);
sel.addRange(r);
}
}
}
}
},
_changeToStep: function(from, to){
// summary:
// Reverts editor to "to" setting, from the undo stack.
// tags:
// private
this.setValue(to.text);
var b = to.bookmark;
if(!b){
return;
}
this._moveToBookmark(b);
},
undo: function(){
// summary:
// Handler for editor undo (ex: ctrl-z) operation
// tags:
// private
var ret = false;
if(!this._undoRedoActive){
this._undoRedoActive = true;
this.endEditing(true);
var s = this._steps.pop();
if(s && this._steps.length > 0){
this.focus();
this._changeToStep(s, this._steps[this._steps.length - 1]);
this._undoedSteps.push(s);
this.onDisplayChanged();
delete this._undoRedoActive;
ret = true;
}
delete this._undoRedoActive;
}
return ret;
},
redo: function(){
// summary:
// Handler for editor redo (ex: ctrl-y) operation
// tags:
// private
var ret = false;
if(!this._undoRedoActive){
this._undoRedoActive = true;
this.endEditing(true);
var s = this._undoedSteps.pop();
if(s && this._steps.length > 0){
this.focus();
this._changeToStep(this._steps[this._steps.length - 1], s);
this._steps.push(s);
this.onDisplayChanged();
ret = true;
}
delete this._undoRedoActive;
}
return ret;
},
endEditing: function(ignore_caret){
// summary:
// Called to note that the user has stopped typing alphanumeric characters, if it's not already noted.
// Deals with saving undo; see editActionInterval parameter.
// tags:
// private
if(this._editTimer){
this._editTimer = this._editTimer.remove();
}
if(this._inEditing){
this._endEditing(ignore_caret);
this._inEditing = false;
}
},
_getBookmark: function(){
// summary:
// Get the currently selected text
// tags:
// protected
var b = this.selection.getBookmark();
var tmp = [];
if(b && b.mark){
var mark = b.mark;
if(has("ie") < 9 || (has("ie") === 9 && has("quirks"))){
// Try to use the pseudo range API on IE for better accuracy.
var sel = rangeapi.getSelection(this.window);
if(!lang.isArray(mark)){
if(sel){
var range;
if(sel.rangeCount){
range = sel.getRangeAt(0);
}
if(range){
b.mark = range.cloneRange();
}else{
b.mark = this.selection.getBookmark();
}
}
}else{
// Control ranges (img, table, etc), handle differently.
array.forEach(b.mark, function(n){
tmp.push(rangeapi.getIndex(n, this.editNode).o);
}, this);
b.mark = tmp;
}
}
try{
if(b.mark && b.mark.startContainer){
tmp = rangeapi.getIndex(b.mark.startContainer, this.editNode).o;
b.mark = {startContainer: tmp,
startOffset: b.mark.startOffset,
endContainer: b.mark.endContainer === b.mark.startContainer ? tmp : rangeapi.getIndex(b.mark.endContainer, this.editNode).o,
endOffset: b.mark.endOffset};
}
}catch(e){
b.mark = null;
}
}
return b;
},
_beginEditing: function(){
// summary:
// Called when the user starts typing alphanumeric characters.
// Deals with saving undo; see editActionInterval parameter.
// tags:
// private
if(this._steps.length === 0){
// You want to use the editor content without post filtering
// to make sure selection restores right for the 'initial' state.
// and undo is called. So not using this.value, as it was 'processed'
// and the line-up for selections may have been altered.
this._steps.push({'text': html.getChildrenHtml(this.editNode), 'bookmark': this._getBookmark()});
}
},
_endEditing: function(){
// summary:
// Called when the user stops typing alphanumeric characters.
// Deals with saving undo; see editActionInterval parameter.
// tags:
// private
// Avoid filtering to make sure selections restore.
var v = html.getChildrenHtml(this.editNode);
this._undoedSteps = [];//clear undoed steps
this._steps.push({text: v, bookmark: this._getBookmark()});
},
onKeyDown: function(e){
// summary:
// Handler for onkeydown event.
// tags:
// private
//We need to save selection if the user TAB away from this editor
//no need to call _saveSelection for IE, as that will be taken care of in onBeforeDeactivate
if(!has("ie") && !this.iframe && e.keyCode == keys.TAB && !this.tabIndent){
this._saveSelection();
}
if(!this.customUndo){
this.inherited(arguments);
return;
}
var k = e.keyCode;
if(e.ctrlKey && !e.shiftKey && !e.altKey){//undo and redo only if the special right Alt + z/y are not pressed #5892
if(k == 90 || k == 122){ //z, but also F11 key
e.stopPropagation();
e.preventDefault();
this.undo();
return;
}else if(k == 89 || k == 121){ //y
e.stopPropagation();
e.preventDefault();
this.redo();
return;
}
}
this.inherited(arguments);
switch(k){
case keys.ENTER:
case keys.BACKSPACE:
case keys.DELETE:
this.beginEditing();
break;
case 88: //x
case 86: //v
if(e.ctrlKey && !e.altKey && !e.metaKey){
this.endEditing();//end current typing step if any
if(e.keyCode == 88){
this.beginEditing('cut');
}else{
this.beginEditing('paste');
}
//use timeout to trigger after the paste is complete
this.defer("endEditing", 1);
break;
}
//pass through
default:
if(!e.ctrlKey && !e.altKey && !e.metaKey && (e.keyCode < keys.F1 || e.keyCode > keys.F15)){
this.beginEditing();
break;
}
//pass through
case keys.ALT:
this.endEditing();
break;
case keys.UP_ARROW:
case keys.DOWN_ARROW:
case keys.LEFT_ARROW:
case keys.RIGHT_ARROW:
case keys.HOME:
case keys.END:
case keys.PAGE_UP:
case keys.PAGE_DOWN:
this.endEditing(true);
break;
//maybe ctrl+backspace/delete, so don't endEditing when ctrl is pressed
case keys.CTRL:
case keys.SHIFT:
case keys.TAB:
break;
}
},
_onBlur: function(){
// summary:
// Called from focus manager when focus has moved away from this editor
// tags:
// protected
//this._saveSelection();
this.inherited(arguments);
this.endEditing(true);
},
_saveSelection: function(){
// summary:
// Save the currently selected text in _savedSelection attribute
// tags:
// private
try{
this._savedSelection = this._getBookmark();
}catch(e){ /* Squelch any errors that occur if selection save occurs due to being hidden simultaneously. */
}
},
_restoreSelection: function(){
// summary:
// Re-select the text specified in _savedSelection attribute;
// see _saveSelection().
// tags:
// private
if(this._savedSelection){
// Clear off cursor to start, we're deliberately going to a selection.
delete this._cursorToStart;
// only restore the selection if the current range is collapsed
// if not collapsed, then it means the editor does not lose
// selection and there is no need to restore it
if(this.selection.isCollapsed()){
this._moveToBookmark(this._savedSelection);
}
delete this._savedSelection;
}
},
onClick: function(){
// summary:
// Handler for when editor is clicked
// tags:
// protected
this.endEditing(true);
this.inherited(arguments);
},
replaceValue: function(/*String*/ html){
// summary:
// over-ride of replaceValue to support custom undo and stack maintenance.
// tags:
// protected
if(!this.customUndo){
this.inherited(arguments);
}else{
if(this.isClosed){
this.setValue(html);
}else{
this.beginEditing();
if(!html){
html = " "; //
}
this.setValue(html);
this.endEditing();
}
}
},
_setDisabledAttr: function(/*Boolean*/ value){
this.setValueDeferred.then(lang.hitch(this, function(){
if((!this.disabled && value) || (!this._buttonEnabledPlugins && value)){
// Disable editor: disable all enabled buttons and remember that list
array.forEach(this._plugins, function(p){
p.set("disabled", true);
});
}else if(this.disabled && !value){
// Restore plugins to being active.
array.forEach(this._plugins, function(p){
p.set("disabled", false);
});
}
}));
this.inherited(arguments);
},
_setStateClass: function(){
try{
this.inherited(arguments);
// Let theme set the editor's text color based on editor enabled/disabled state.
// We need to jump through hoops because the main document (where the theme CSS is)
// is separate from the iframe's document.
if(this.document && this.document.body){
domStyle.set(this.document.body, "color", domStyle.get(this.iframe, "color"));
domStyle.set(this.document.body, "background-color", domStyle.get(this.iframe, "background-color"));
}
}catch(e){ /* Squelch any errors caused by focus change if hidden during a state change */
}
}
});
// Register the "default plugins", ie, the built-in editor commands
function simplePluginFactory(args){
return new _Plugin({ command: args.name });
}
function togglePluginFactory(args){
return new _Plugin({ buttonClass: ToggleButton, command: args.name });
}
lang.mixin(_Plugin.registry, {
"undo": simplePluginFactory,
"redo": simplePluginFactory,
"cut": simplePluginFactory,
"copy": simplePluginFactory,
"paste": simplePluginFactory,
"insertOrderedList": simplePluginFactory,
"insertUnorderedList": simplePluginFactory,
"indent": simplePluginFactory,
"outdent": simplePluginFactory,
"justifyCenter": simplePluginFactory,
"justifyFull": simplePluginFactory,
"justifyLeft": simplePluginFactory,
"justifyRight": simplePluginFactory,
"delete": simplePluginFactory,
"selectAll": simplePluginFactory,
"removeFormat": simplePluginFactory,
"unlink": simplePluginFactory,
"insertHorizontalRule": simplePluginFactory,
"bold": togglePluginFactory,
"italic": togglePluginFactory,
"underline": togglePluginFactory,
"strikethrough": togglePluginFactory,
"subscript": togglePluginFactory,
"superscript": togglePluginFactory,
"|": function(){
return new _Plugin({
setEditor: function(editor){
this.editor = editor;
this.button = new ToolbarSeparator({ownerDocument: editor.ownerDocument});
}
});
}
});
return Editor;
});