]>
git.wh0rd.org - tt-rss.git/blob - lib/dijit/Editor.js.uncompressed.js
1 define("dijit/Editor", [
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
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()
22 "./layout/_LayoutWidget",
23 "./form/ToggleButton",
25 "./_editor/plugins/EnterKeyHandling",
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
){
39 var Editor
= declare("dijit.Editor", RichText
, {
41 // A rich text Editing widget
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.
52 // plugins: [const] Object[]
53 // A list of plugin names (as strings) or instances (as objects)
56 // When declared in markup, it might look like:
57 // | plugins="['bold',{name:'dijit._editor.plugins.FontChoice', command:'fontName', generic:true}]"
60 // extraPlugins: [const] Object[]
61 // A list of extra plugin names which will be appended to plugins array
64 constructor: function(/*===== params, srcNodeRef =====*/){
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.
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"*/];
79 this._editInterval
= this.editActionInterval
* 1000;
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.
92 this.events
.push("onBeforeDeactivate");
93 this.events
.push("onBeforeActivate");
97 postMixInProperties: function(){
99 // Extension to make sure a deferred is in place before certain functions
100 // execute, like making sure all the plugins are properly inserted.
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
);
109 postCreate: function(){
110 //for custom undo/redo, if enabled.
111 this._steps
=this._steps
.slice(0);
112 this._undoedSteps
=this._undoedSteps
.slice(0);
114 if(lang
.isArray(this.extraPlugins
)){
115 this.plugins
=this.plugins
.concat(this.extraPlugins
);
118 this.inherited(arguments
);
120 this.commands
= i18n
.getLocalization("dijit._editor", "commands", this.lang
);
123 // if we haven't been assigned a toolbar, create one
124 this.toolbar
= new Toolbar({
125 ownerDocument
: this.ownerDocument
,
129 this.header
.appendChild(this.toolbar
.domNode
);
132 array
.forEach(this.plugins
, this.addPlugin
, this);
134 // Okay, denote the value can now be set.
135 this.setValueDeferred
.resolve(true);
137 domClass
.add(this.iframe
.parentNode
, "dijitEditorIFrameContainer");
138 domClass
.add(this.iframe
, "dijitEditorIFrame");
139 domAttr
.set(this.iframe
, "allowTransparency", true);
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");
147 this.toolbar
.startup();
148 this.onNormalizedDisplayChanged(); //update toolbar button status
151 array
.forEach(this._plugins
, function(p
){
157 this.toolbar
.destroyRecursive();
159 this.inherited(arguments
);
161 addPlugin: function(/*String||Object||Function*/ plugin
, /*Integer?*/ index
){
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.
170 // String, args object, plugin instance, or plugin constructor
172 // This object will be passed to the plugin constructor
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
;
179 var o
={"args":args
,"plugin":null,"editor":this};
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
);
188 topic
.publish(dijit
._scopeName
+ ".Editor.getPlugin", o
); // publish
193 // TODO: remove lang.getObject() call in 2.0
194 var pc
= args
.ctor
|| lang
.getObject(args
.name
) || require(args
.name
);
196 o
.plugin
= new pc(args
);
199 throw new Error(this.id
+ ": cannot find plugin [" + args
.name
+ "]");
203 throw new Error(this.id
+ ": cannot find plugin [" + args
.name
+ "]");
207 if(arguments
.length
> 1){
208 this._plugins
[index
] = plugin
;
210 this._plugins
.push(plugin
);
212 plugin
.setEditor(this);
213 if(lang
.isFunction(plugin
.setToolbar
)){
214 plugin
.setToolbar(this.toolbar
);
218 //the following 2 functions are required to make the editor play nice under a layout widget, see #4070
220 resize: function(size
){
222 // Resize the editor to the specified size, see `dijit/layout/_LayoutWidget.resize()`
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
);
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
237 // Called from `dijit/layout/_LayoutWidget.resize()`. This shouldn't be called directly
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";
251 this.iframe
.style
.height
="100%";
253 this._layoutMode
= true;
256 _onIEMouseDown: function(/*Event*/ e
){
258 // IE only to prevent 2 clicks to focus
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
;
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;
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;
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;
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");
299 this.inherited(arguments
);
302 onBeforeActivate: function(){
303 this._restoreSelection();
305 onBeforeDeactivate: function(e
){
307 // Called on IE right before focus is lost. Saves the selected range.
311 this.endEditing(true);
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();
318 //console.log('onBeforeDeactivate',this);
321 /* beginning of custom undo/redo support */
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.
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
338 editActionInterval: 3,
340 beginEditing: function(cmd){
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.
346 if(!this._inEditing){
347 this._inEditing=true;
348 this._beginEditing(cmd);
350 if(this.editActionInterval>0){
352 this._editTimer.remove();
354 this._editTimer = this.defer("endEditing", this._editInterval);
358 // TODO: declaring these in the prototype is meaningless, just create in the constructor/postCreate
362 execCommand: function(cmd){
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.
368 if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){
373 this._beginEditing();
375 var r = this.inherited(arguments);
383 _pasteImpl: function(){
385 // Over-ride of paste command control to make execCommand cleaner
388 return this._clipboardCommand("paste");
391 _cutImpl: function(){
393 // Over-ride of cut command control to make execCommand cleaner
396 return this._clipboardCommand("cut");
399 _copyImpl: function(){
401 // Over-ride of copy command control to make execCommand cleaner
404 return this._clipboardCommand("copy");
407 _clipboardCommand: function(cmd){
409 // Function to handle processing clipboard commands (or at least try to).
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
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
]])]));
434 queryCommandEnabled: function(cmd
){
436 // Returns true if specified editor command is enabled.
437 // Used by the plugins to know when to highlight/not highlight buttons.
440 if(this.customUndo
&& (cmd
== 'undo' || cmd
== 'redo')){
441 return cmd
== 'undo' ? (this._steps
.length
> 1) : (this._undoedSteps
.length
> 0);
443 return this.inherited(arguments
);
446 _moveToBookmark: function(b
){
448 // Selects the text specified in bookmark b
451 var bookmark
= b
.mark
;
453 var col
= b
.isCollapsed
;
454 var r
, sNode
, eNode
, sel
;
457 if(lang
.isArray(mark
)){
458 //IE CONTROL, have to use the native bookmark.
460 array
.forEach(mark
,function(n
){
461 bookmark
.push(rangeapi
.getNode(n
,this.editNode
));
463 win
.withGlobal(this.window
,'moveToBookmark',focusBase
,[{mark
: bookmark
, isCollapsed
: col
}]);
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
);
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
);
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
);
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
);
505 _changeToStep: function(from, to
){
507 // Reverts editor to "to" setting, from the undo stack.
510 this.setValue(to
.text
);
513 this._moveToBookmark(b
);
517 // Handler for editor undo (ex: ctrl-z) operation
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){
527 this._changeToStep(s
,this._steps
[this._steps
.length
-1]);
528 this._undoedSteps
.push(s
);
529 this.onDisplayChanged();
530 delete this._undoRedoActive
;
533 delete this._undoRedoActive
;
539 // Handler for editor redo (ex: ctrl-y) operation
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){
549 this._changeToStep(this._steps
[this._steps
.length
-1],s
);
551 this.onDisplayChanged();
554 delete this._undoRedoActive
;
558 endEditing: function(ignore_caret
){
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.
565 this._editTimer
= this._editTimer
.remove();
568 this._endEditing(ignore_caret
);
569 this._inEditing
=false;
573 _getBookmark: function(){
575 // Get the currently selected text
578 var b
=win
.withGlobal(this.window
,focusBase
.getBookmark
);
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
)){
589 range
= sel
.getRangeAt(0);
592 b
.mark
= range
.cloneRange();
594 b
.mark
= win
.withGlobal(this.window
,focusBase
.getBookmark
);
598 // Control ranges (img, table, etc), handle differently.
599 array
.forEach(b
.mark
,function(n
){
600 tmp
.push(rangeapi
.getIndex(n
,this.editNode
).o
);
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
};
619 _beginEditing: function(){
621 // Called when the user starts typing alphanumeric characters.
622 // Deals with saving undo; see editActionInterval parameter.
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()});
633 _endEditing: function(){
635 // Called when the user stops typing alphanumeric characters.
636 // Deals with saving undo; see editActionInterval parameter.
640 // Avoid filtering to make sure selections restore.
641 var v
= html
.getChildrenHtml(this.editNode
);
643 this._undoedSteps
=[];//clear undoed steps
644 this._steps
.push({text
: v
, bookmark
: this._getBookmark()});
646 onKeyDown: function(e
){
648 // Handler for onkeydown event.
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();
657 if(!this.customUndo
){
658 this.inherited(arguments
);
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
667 }else if(k
== 89 || k
== 121){ //y
673 this.inherited(arguments
);
683 if(e
.ctrlKey
&& !e
.altKey
&& !e
.metaKey
){
684 this.endEditing();//end current typing step if any
686 this.beginEditing('cut');
688 this.beginEditing('paste');
690 //use timeout to trigger after the paste is complete
691 this.defer("endEditing", 1);
696 if(!e
.ctrlKey
&& !e
.altKey
&& !e
.metaKey
&& (e
.keyCode
<keys
.F1
|| e
.keyCode
>keys
.F15
)){
705 case keys
.DOWN_ARROW
:
706 case keys
.LEFT_ARROW
:
707 case keys
.RIGHT_ARROW
:
712 this.endEditing(true);
714 //maybe ctrl+backspace/delete, so don't endEditing when ctrl is pressed
723 // Called from focus manager when focus has moved away from this editor
727 //this._saveSelection();
728 this.inherited(arguments
);
729 this.endEditing(true);
731 _saveSelection: function(){
733 // Save the currently selected text in _savedSelection attribute
737 this._savedSelection
=this._getBookmark();
738 }catch(e
){ /* Squelch any errors that occur if selection save occurs due to being hidden simultaneously. */}
740 _restoreSelection: function(){
742 // Re-select the text specified in _savedSelection attribute;
743 // see _saveSelection().
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
);
755 delete this._savedSelection
;
761 // Handler for when editor is clicked
764 this.endEditing(true);
765 this.inherited(arguments
);
768 replaceValue: function(/*String*/ html
){
770 // over-ride of replaceValue to support custom undo and stack maintenance.
773 if(!this.customUndo
){
774 this.inherited(arguments
);
781 html
= " "; //
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);
796 }else if(this.disabled
&& !value
){
797 // Restore plugins to being active.
798 array
.forEach(this._plugins
, function(p
){
799 p
.set("disabled", false);
803 this.inherited(arguments
);
806 _setStateClass: function(){
808 this.inherited(arguments
);
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"));
816 }catch(e
){ /* Squelch any errors caused by focus change if hidden during a state change */}
820 // Register the "default plugins", ie, the built-in editor commands
821 function simplePluginFactory(args
){
822 return new _Plugin({ command
: args
.name
});
824 function togglePluginFactory(args
){
825 return new _Plugin({ buttonClass
: ToggleButton
, command
: args
.name
});
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
,
847 "bold": togglePluginFactory
,
848 "italic": togglePluginFactory
,
849 "underline": togglePluginFactory
,
850 "strikethrough": togglePluginFactory
,
851 "subscript": togglePluginFactory
,
852 "superscript": togglePluginFactory
,
856 setEditor: function(editor
){
857 this.editor
= editor
;
858 this.button
= new ToolbarSeparator({ownerDocument
: editor
.ownerDocument
});