]> git.immae.eu Git - perso/Immae/Projets/packagist/ludivine-ckeditor-component.git/blob - sources/plugins/indent/plugin.js
Validation initiale
[perso/Immae/Projets/packagist/ludivine-ckeditor-component.git] / sources / plugins / indent / plugin.js
1 /**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6 /**
7 * @fileOverview Increase and Decrease Indent commands.
8 */
9
10 ( function() {
11 'use strict';
12
13 var TRISTATE_DISABLED = CKEDITOR.TRISTATE_DISABLED,
14 TRISTATE_OFF = CKEDITOR.TRISTATE_OFF;
15
16 CKEDITOR.plugins.add( 'indent', {
17 // jscs:disable maximumLineLength
18 lang: 'af,ar,az,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,oc,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%
22
23 init: function( editor ) {
24 var genericDefinition = CKEDITOR.plugins.indent.genericDefinition;
25
26 // Register generic commands.
27 setupGenericListeners( editor, editor.addCommand( 'indent', new genericDefinition( true ) ) );
28 setupGenericListeners( editor, editor.addCommand( 'outdent', new genericDefinition() ) );
29
30 // Create and register toolbar button if possible.
31 if ( editor.ui.addButton ) {
32 editor.ui.addButton( 'Indent', {
33 label: editor.lang.indent.indent,
34 command: 'indent',
35 directional: true,
36 toolbar: 'indent,20'
37 } );
38
39 editor.ui.addButton( 'Outdent', {
40 label: editor.lang.indent.outdent,
41 command: 'outdent',
42 directional: true,
43 toolbar: 'indent,10'
44 } );
45 }
46
47 // Register dirChanged listener.
48 editor.on( 'dirChanged', function( evt ) {
49 var range = editor.createRange(),
50 dataNode = evt.data.node;
51
52 range.setStartBefore( dataNode );
53 range.setEndAfter( dataNode );
54
55 var walker = new CKEDITOR.dom.walker( range ),
56 node;
57
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 );
64 continue;
65 }
66
67 // Switch alignment classes.
68 var classes = editor.config.indentClasses;
69 if ( classes ) {
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 ] );
75 }
76 }
77 }
78
79 // Switch the margins.
80 var marginLeft = node.getStyle( 'margin-right' ),
81 marginRight = node.getStyle( 'margin-left' );
82
83 marginLeft ? node.setStyle( 'margin-left', marginLeft ) : node.removeStyle( 'margin-left' );
84 marginRight ? node.setStyle( 'margin-right', marginRight ) : node.removeStyle( 'margin-right' );
85 }
86 }
87 } );
88 }
89 } );
90
91 /**
92 * Global command class definitions and global helpers.
93 *
94 * @class
95 * @singleton
96 */
97 CKEDITOR.plugins.indent = {
98 /**
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
101 * UI states.
102 *
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).
105 *
106 * @class CKEDITOR.plugins.indent.genericDefinition
107 * @extends CKEDITOR.commandDefinition
108 * @param {CKEDITOR.editor} editor The editor instance this command will be
109 * applied to.
110 * @param {String} name The name of the command.
111 * @param {Boolean} [isIndent] Defines the command as indenting or outdenting.
112 */
113 genericDefinition: function( isIndent ) {
114 /**
115 * Determines whether the command belongs to the indentation family.
116 * Otherwise it is assumed to be an outdenting command.
117 *
118 * @readonly
119 * @property {Boolean} [=false]
120 */
121 this.isIndent = !!isIndent;
122
123 // Mimic naive startDisabled behavior for outdent.
124 this.startDisabled = !this.isIndent;
125 },
126
127 /**
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.
131 *
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.
135 *
136 * **NOTE**: This is not an {@link CKEDITOR.command editor command}.
137 * Context-specific commands are internal, for indentation system only.
138 *
139 * @class CKEDITOR.plugins.indent.specificDefinition
140 * @param {CKEDITOR.editor} editor The editor instance this command will be
141 * applied to.
142 * @param {String} name The name of the command.
143 * @param {Boolean} [isIndent] Defines the command as indenting or outdenting.
144 */
145 specificDefinition: function( editor, name, isIndent ) {
146 this.name = name;
147 this.editor = editor;
148
149 /**
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.
152 *
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.
158 *
159 * **Note**: This function must return either {@link CKEDITOR#TRISTATE_DISABLED}
160 * or {@link CKEDITOR#TRISTATE_OFF}.
161 *
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}.
166 *
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.
169 *
170 * Sample definition:
171 *
172 * command.jobs = {
173 * // Priority = 20.
174 * '20': {
175 * refresh( editor, path ) {
176 * if ( condition )
177 * return CKEDITOR.TRISTATE_OFF;
178 * else
179 * return CKEDITOR.TRISTATE_DISABLED;
180 * },
181 * exec( editor ) {
182 * // DOM modified! This was OK.
183 * return true;
184 * }
185 * },
186 * // Priority = 60. This job is done later.
187 * '60': {
188 * // Another job.
189 * }
190 * };
191 *
192 * For additional information, please check comments for
193 * the `setupGenericListeners` function.
194 *
195 * @readonly
196 * @property {Object} [={}]
197 */
198 this.jobs = {};
199
200 /**
201 * Determines whether the editor that the command belongs to has
202 * {@link CKEDITOR.config#enterMode config.enterMode} set to {@link CKEDITOR#ENTER_BR}.
203 *
204 * @readonly
205 * @see CKEDITOR.config#enterMode
206 * @property {Boolean} [=false]
207 */
208 this.enterBr = editor.config.enterMode == CKEDITOR.ENTER_BR;
209
210 /**
211 * Determines whether the command belongs to the indentation family.
212 * Otherwise it is assumed to be an outdenting command.
213 *
214 * @readonly
215 * @property {Boolean} [=false]
216 */
217 this.isIndent = !!isIndent;
218
219 /**
220 * The name of the global command related to this one.
221 *
222 * @readonly
223 */
224 this.relatedGlobal = isIndent ? 'indent' : 'outdent';
225
226 /**
227 * A keystroke associated with this command (*Tab* or *Shift+Tab*).
228 *
229 * @readonly
230 */
231 this.indentKey = isIndent ? 9 : CKEDITOR.SHIFT + 9;
232
233 /**
234 * Stores created markers for the command so they can eventually be
235 * purged after the `exec` function is run.
236 */
237 this.database = {};
238 },
239
240 /**
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.
244 *
245 * CKEDITOR.plugins.indent.registerCommands( editor, {
246 * 'indentlist': new indentListCommand( editor, 'indentlist' ),
247 * 'outdentlist': new indentListCommand( editor, 'outdentlist' )
248 * } );
249 *
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.
253 *
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.
257 *
258 * @member CKEDITOR.plugins.indent
259 * @param {CKEDITOR.editor} editor The editor instance this command is
260 * applied to.
261 * @param {Object} commands An object of {@link CKEDITOR.command}.
262 */
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 );
268
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 ) {
275 if ( evt.data.done )
276 return;
277
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' );
282
283 if ( command.execJob( editor, priority ) )
284 evt.data.done = true;
285
286 editor.fire( 'unlockSnapshot' );
287
288 // Clean up the markers.
289 CKEDITOR.dom.element.clearAllMarkers( command.database );
290 }, this, null, priority );
291
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 = {};
298
299 evt.data.states[ command.name + '@' + priority ] =
300 command.refreshJob( editor, priority, evt.data.path );
301 }, this, null, priority );
302 }
303
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 ] );
308 }
309 } );
310 }
311 };
312
313 CKEDITOR.plugins.indent.genericDefinition.prototype = {
314 context: 'p',
315
316 exec: function() {}
317 };
318
319 CKEDITOR.plugins.indent.specificDefinition.prototype = {
320 /**
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.
324 *
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.
329 */
330 execJob: function( editor, priority ) {
331 var job = this.jobs[ priority ];
332
333 if ( job.state != TRISTATE_DISABLED )
334 return job.exec.call( this, editor );
335 },
336
337 /**
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}.
341 *
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.
346 */
347 refreshJob: function( editor, priority, path ) {
348 var job = this.jobs[ priority ];
349
350 if ( !editor.activeFilter.checkFeature( this ) )
351 job.state = TRISTATE_DISABLED;
352 else
353 job.state = job.refresh.call( this, editor, path );
354
355 return job.state;
356 },
357
358 /**
359 * Checks if the element path contains the element handled
360 * by this indentation command.
361 *
362 * @param {CKEDITOR.dom.elementPath} node A path to be checked.
363 * @returns {CKEDITOR.dom.element}
364 */
365 getContext: function( path ) {
366 return path.contains( this.context );
367 }
368 };
369
370 /**
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.
374 *
375 * Listener priorities are crucial. Different indentation phases
376 * are executed with different priorities.
377 *
378 * For the `exec` event:
379 *
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.
384 *
385 * The visual interpretation looks as follows:
386 *
387 * +------------------+
388 * | Exec event fired |
389 * +------ + ---------+
390 * |
391 * 0 -<----------+ Selection and bookmarks saved.
392 * |
393 * |
394 * 25 -<---+ Exec 1st job of plugin#1 (return false, continuing...).
395 * |
396 * |
397 * 50 -<---+ Exec 1st job of plugin#2 (return false, continuing...).
398 * |
399 * |
400 * 75 -<---+ Exec 2nd job of plugin#1 (only if plugin#2 failed).
401 * |
402 * |
403 * 100 -<-----------+ Re-select bookmarks, clean-up.
404 * |
405 * +-------- v ----------+
406 * | Exec event finished |
407 * +---------------------+
408 *
409 * For the `refresh` event:
410 *
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.
418 *
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}.
422 *
423 * @param {CKEDITOR.command} command The command to be set up.
424 * @private
425 */
426 function setupGenericListeners( editor, command ) {
427 var selection, bookmarks;
428
429 // Set the command state according to content-specific
430 // command states.
431 command.on( 'refresh', function( evt ) {
432 // If no state comes with event data, disable command.
433 var states = [ TRISTATE_DISABLED ];
434
435 for ( var s in evt.data.states )
436 states.push( evt.data.states[ s ] );
437
438 this.setState( CKEDITOR.tools.search( states, TRISTATE_OFF ) ? TRISTATE_OFF : TRISTATE_DISABLED );
439 }, command, null, 100 );
440
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 );
446
447 // Mark execution as not handled yet.
448 if ( !evt.data )
449 evt.data = {};
450
451 evt.data.done = false;
452 }, command, null, 0 );
453
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 );
460 }
461 } )();