2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
7 * @fileOverview Increase and Decrease Indent commands.
13 var TRISTATE_DISABLED
= CKEDITOR
.TRISTATE_DISABLED
,
14 TRISTATE_OFF
= CKEDITOR
.TRISTATE_OFF
;
16 CKEDITOR
.plugins
.add( 'indent', {
17 // jscs:disable maximumLineLength
18 lang
: 'af,ar,bg,bn,bs,ca,cs,cy,da,de,de-ch,el,en,en-au,en-ca,en-gb,eo,es,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
19 // jscs:enable maximumLineLength
20 icons
: 'indent,indent-rtl,outdent,outdent-rtl', // %REMOVE_LINE_CORE%
21 hidpi
: true, // %REMOVE_LINE_CORE%
23 init: function( editor
) {
24 var genericDefinition
= CKEDITOR
.plugins
.indent
.genericDefinition
;
26 // Register generic commands.
27 setupGenericListeners( editor
, editor
.addCommand( 'indent', new genericDefinition( true ) ) );
28 setupGenericListeners( editor
, editor
.addCommand( 'outdent', new genericDefinition() ) );
30 // Create and register toolbar button if possible.
31 if ( editor
.ui
.addButton
) {
32 editor
.ui
.addButton( 'Indent', {
33 label
: editor
.lang
.indent
.indent
,
39 editor
.ui
.addButton( 'Outdent', {
40 label
: editor
.lang
.indent
.outdent
,
47 // Register dirChanged listener.
48 editor
.on( 'dirChanged', function( evt
) {
49 var range
= editor
.createRange(),
50 dataNode
= evt
.data
.node
;
52 range
.setStartBefore( dataNode
);
53 range
.setEndAfter( dataNode
);
55 var walker
= new CKEDITOR
.dom
.walker( range
),
58 while ( ( node
= walker
.next() ) ) {
59 if ( node
.type
== CKEDITOR
.NODE_ELEMENT
) {
60 // A child with the defined dir is to be ignored.
61 if ( !node
.equals( dataNode
) && node
.getDirection() ) {
62 range
.setStartAfter( node
);
63 walker
= new CKEDITOR
.dom
.walker( range
);
67 // Switch alignment classes.
68 var classes
= editor
.config
.indentClasses
;
70 var suffix
= ( evt
.data
.dir
== 'ltr' ) ? [ '_rtl', '' ] : [ '', '_rtl' ];
71 for ( var i
= 0; i
< classes
.length
; i
++ ) {
72 if ( node
.hasClass( classes
[ i
] + suffix
[ 0 ] ) ) {
73 node
.removeClass( classes
[ i
] + suffix
[ 0 ] );
74 node
.addClass( classes
[ i
] + suffix
[ 1 ] );
79 // Switch the margins.
80 var marginLeft
= node
.getStyle( 'margin-right' ),
81 marginRight
= node
.getStyle( 'margin-left' );
83 marginLeft
? node
.setStyle( 'margin-left', marginLeft
) : node
.removeStyle( 'margin-left' );
84 marginRight
? node
.setStyle( 'margin-right', marginRight
) : node
.removeStyle( 'margin-right' );
92 * Global command class definitions and global helpers.
97 CKEDITOR
.plugins
.indent
= {
99 * A base class for a generic command definition, responsible mainly for creating
100 * Increase Indent and Decrease Indent toolbar buttons as well as for refreshing
103 * Commands of this class do not perform any indentation by themselves. They
104 * delegate this job to content-specific indentation commands (i.e. indentlist).
106 * @class CKEDITOR.plugins.indent.genericDefinition
107 * @extends CKEDITOR.commandDefinition
108 * @param {CKEDITOR.editor} editor The editor instance this command will be
110 * @param {String} name The name of the command.
111 * @param {Boolean} [isIndent] Defines the command as indenting or outdenting.
113 genericDefinition: function( isIndent
) {
115 * Determines whether the command belongs to the indentation family.
116 * Otherwise it is assumed to be an outdenting command.
119 * @property {Boolean} [=false]
121 this.isIndent
= !!isIndent
;
123 // Mimic naive startDisabled behavior for outdent.
124 this.startDisabled
= !this.isIndent
;
128 * A base class for specific indentation command definitions responsible for
129 * handling a pre-defined set of elements i.e. indentlist for lists or
130 * indentblock for text block elements.
132 * Commands of this class perform indentation operations and modify the DOM structure.
133 * They listen for events fired by {@link CKEDITOR.plugins.indent.genericDefinition}
134 * and execute defined actions.
136 * **NOTE**: This is not an {@link CKEDITOR.command editor command}.
137 * Context-specific commands are internal, for indentation system only.
139 * @class CKEDITOR.plugins.indent.specificDefinition
140 * @param {CKEDITOR.editor} editor The editor instance this command will be
142 * @param {String} name The name of the command.
143 * @param {Boolean} [isIndent] Defines the command as indenting or outdenting.
145 specificDefinition: function( editor
, name
, isIndent
) {
147 this.editor
= editor
;
150 * An object of jobs handled by the command. Each job consists
151 * of two functions: `refresh` and `exec` as well as the execution priority.
153 * * The `refresh` function determines whether a job is doable for
154 * a particular context. These functions are executed in the
155 * order of priorities, one by one, for all plugins that registered
156 * jobs. As jobs are related to generic commands, refreshing
157 * occurs when the global command is firing the `refresh` event.
159 * **Note**: This function must return either {@link CKEDITOR#TRISTATE_DISABLED}
160 * or {@link CKEDITOR#TRISTATE_OFF}.
162 * * The `exec` function modifies the DOM if possible. Just like
163 * `refresh`, `exec` functions are executed in the order of priorities
164 * while the generic command is executed. This function is not executed
165 * if `refresh` for this job returned {@link CKEDITOR#TRISTATE_DISABLED}.
167 * **Note**: This function must return a Boolean value, indicating whether it
168 * was successful. If a job was successful, then no other jobs are being executed.
175 * refresh( editor, path ) {
177 * return CKEDITOR.TRISTATE_OFF;
179 * return CKEDITOR.TRISTATE_DISABLED;
182 * // DOM modified! This was OK.
186 * // Priority = 60. This job is done later.
192 * For additional information, please check comments for
193 * the `setupGenericListeners` function.
196 * @property {Object} [={}]
201 * Determines whether the editor that the command belongs to has
202 * {@link CKEDITOR.config#enterMode config.enterMode} set to {@link CKEDITOR#ENTER_BR}.
205 * @see CKEDITOR.config#enterMode
206 * @property {Boolean} [=false]
208 this.enterBr
= editor
.config
.enterMode
== CKEDITOR
.ENTER_BR
;
211 * Determines whether the command belongs to the indentation family.
212 * Otherwise it is assumed to be an outdenting command.
215 * @property {Boolean} [=false]
217 this.isIndent
= !!isIndent
;
220 * The name of the global command related to this one.
224 this.relatedGlobal
= isIndent
? 'indent' : 'outdent';
227 * A keystroke associated with this command (*Tab* or *Shift+Tab*).
231 this.indentKey
= isIndent
? 9 : CKEDITOR
.SHIFT
+ 9;
234 * Stores created markers for the command so they can eventually be
235 * purged after the `exec` function is run.
241 * Registers content-specific commands as a part of the indentation system
242 * directed by generic commands. Once a command is registered,
243 * it listens for events of a related generic command.
245 * CKEDITOR.plugins.indent.registerCommands( editor, {
246 * 'indentlist': new indentListCommand( editor, 'indentlist' ),
247 * 'outdentlist': new indentListCommand( editor, 'outdentlist' )
250 * Content-specific commands listen for the generic command's `exec` and
251 * try to execute their own jobs, one after another. If some execution is
252 * successful, `evt.data.done` is set so no more jobs (commands) are involved.
254 * Content-specific commands also listen for the generic command's `refresh`
255 * and fill the `evt.data.states` object with states of jobs. A generic command
256 * uses this data to determine its own state and to update the UI.
258 * @member CKEDITOR.plugins.indent
259 * @param {CKEDITOR.editor} editor The editor instance this command is
261 * @param {Object} commands An object of {@link CKEDITOR.command}.
263 registerCommands: function( editor
, commands
) {
264 editor
.on( 'pluginsLoaded', function() {
265 for ( var name
in commands
) {
266 ( function( editor
, command
) {
267 var relatedGlobal
= editor
.getCommand( command
.relatedGlobal
);
269 for ( var priority
in command
.jobs
) {
270 // Observe generic exec event and execute command when necessary.
271 // If the command was successfully handled by the command and
272 // DOM has been modified, stop event propagation so no other plugin
273 // will bother. Job is done.
274 relatedGlobal
.on( 'exec', function( evt
) {
278 // Make sure that anything this command will do is invisible
279 // for undoManager. What undoManager only can see and
280 // remember is the execution of the global command (relatedGlobal).
281 editor
.fire( 'lockSnapshot' );
283 if ( command
.execJob( editor
, priority
) )
284 evt
.data
.done
= true;
286 editor
.fire( 'unlockSnapshot' );
288 // Clean up the markers.
289 CKEDITOR
.dom
.element
.clearAllMarkers( command
.database
);
290 }, this, null, priority
);
292 // Observe generic refresh event and force command refresh.
293 // Once refreshed, save command state in event data
294 // so generic command plugin can update its own state and UI.
295 relatedGlobal
.on( 'refresh', function( evt
) {
296 if ( !evt
.data
.states
)
297 evt
.data
.states
= {};
299 evt
.data
.states
[ command
.name
+ '@' + priority
] =
300 command
.refreshJob( editor
, priority
, evt
.data
.path
);
301 }, this, null, priority
);
304 // Since specific indent commands have no UI elements,
305 // they need to be manually registered as a editor feature.
306 editor
.addFeature( command
);
307 } )( this, commands
[ name
] );
313 CKEDITOR
.plugins
.indent
.genericDefinition
.prototype = {
319 CKEDITOR
.plugins
.indent
.specificDefinition
.prototype = {
321 * Executes the content-specific procedure if the context is correct.
322 * It calls the `exec` function of a job of the given `priority`
323 * that modifies the DOM.
325 * @param {CKEDITOR.editor} editor The editor instance this command
326 * will be applied to.
327 * @param {Number} priority The priority of the job to be executed.
328 * @returns {Boolean} Indicates whether the job was successful.
330 execJob: function( editor
, priority
) {
331 var job
= this.jobs
[ priority
];
333 if ( job
.state
!= TRISTATE_DISABLED
)
334 return job
.exec
.call( this, editor
);
338 * Calls the `refresh` function of a job of the given `priority`.
339 * The function returns the state of the job which can be either
340 * {@link CKEDITOR#TRISTATE_DISABLED} or {@link CKEDITOR#TRISTATE_OFF}.
342 * @param {CKEDITOR.editor} editor The editor instance this command
343 * will be applied to.
344 * @param {Number} priority The priority of the job to be executed.
345 * @returns {Number} The state of the job.
347 refreshJob: function( editor
, priority
, path
) {
348 var job
= this.jobs
[ priority
];
350 if ( !editor
.activeFilter
.checkFeature( this ) )
351 job
.state
= TRISTATE_DISABLED
;
353 job
.state
= job
.refresh
.call( this, editor
, path
);
359 * Checks if the element path contains the element handled
360 * by this indentation command.
362 * @param {CKEDITOR.dom.elementPath} node A path to be checked.
363 * @returns {CKEDITOR.dom.element}
365 getContext: function( path
) {
366 return path
.contains( this.context
);
371 * Attaches event listeners for this generic command. Since the indentation
372 * system is event-oriented, generic commands communicate with
373 * content-specific commands using the `exec` and `refresh` events.
375 * Listener priorities are crucial. Different indentation phases
376 * are executed with different priorities.
378 * For the `exec` event:
380 * * 0: Selection and bookmarks are saved by the generic command.
381 * * 1-99: Content-specific commands try to indent the code by executing
382 * their own jobs ({@link CKEDITOR.plugins.indent.specificDefinition#jobs}).
383 * * 100: Bookmarks are re-selected by the generic command.
385 * The visual interpretation looks as follows:
387 * +------------------+
388 * | Exec event fired |
389 * +------ + ---------+
391 * 0 -<----------+ Selection and bookmarks saved.
394 * 25 -<---+ Exec 1st job of plugin#1 (return false, continuing...).
397 * 50 -<---+ Exec 1st job of plugin#2 (return false, continuing...).
400 * 75 -<---+ Exec 2nd job of plugin#1 (only if plugin#2 failed).
403 * 100 -<-----------+ Re-select bookmarks, clean-up.
405 * +-------- v ----------+
406 * | Exec event finished |
407 * +---------------------+
409 * For the `refresh` event:
411 * * <100: Content-specific commands refresh their job states according
412 * to the given path. Jobs save their states in the `evt.data.states` object
413 * passed along with the event. This can be either {@link CKEDITOR#TRISTATE_DISABLED}
414 * or {@link CKEDITOR#TRISTATE_OFF}.
415 * * 100: Command state is determined according to what states
416 * have been returned by content-specific jobs (`evt.data.states`).
417 * UI elements are updated at this stage.
419 * **Note**: If there is at least one job with the {@link CKEDITOR#TRISTATE_OFF} state,
420 * then the generic command state is also {@link CKEDITOR#TRISTATE_OFF}. Otherwise,
421 * the command state is {@link CKEDITOR#TRISTATE_DISABLED}.
423 * @param {CKEDITOR.command} command The command to be set up.
426 function setupGenericListeners( editor
, command
) {
427 var selection
, bookmarks
;
429 // Set the command state according to content-specific
431 command
.on( 'refresh', function( evt
) {
432 // If no state comes with event data, disable command.
433 var states
= [ TRISTATE_DISABLED
];
435 for ( var s
in evt
.data
.states
)
436 states
.push( evt
.data
.states
[ s
] );
438 this.setState( CKEDITOR
.tools
.search( states
, TRISTATE_OFF
) ? TRISTATE_OFF
: TRISTATE_DISABLED
);
439 }, command
, null, 100 );
441 // Initialization. Save bookmarks and mark event as not handled
442 // by any plugin (command) yet.
443 command
.on( 'exec', function( evt
) {
444 selection
= editor
.getSelection();
445 bookmarks
= selection
.createBookmarks( 1 );
447 // Mark execution as not handled yet.
451 evt
.data
.done
= false;
452 }, command
, null, 0 );
454 // Housekeeping. Make sure selectionChange will be called.
455 // Also re-select previously saved bookmarks.
456 command
.on( 'exec', function() {
457 editor
.forceNextSelectionCheck();
458 selection
.selectBookmarks( bookmarks
);
459 }, command
, null, 100 );