]> git.immae.eu Git - perso/Immae/Projets/packagist/ludivine-ckeditor-component.git/blame - sources/plugins/indent/plugin.js
Update to 4.7.3
[perso/Immae/Projets/packagist/ludivine-ckeditor-component.git] / sources / plugins / indent / plugin.js
CommitLineData
c63493c8
IB
1/**\r
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.\r
3 * For licensing, see LICENSE.md or http://ckeditor.com/license\r
4 */\r
5\r
6/**\r
7 * @fileOverview Increase and Decrease Indent commands.\r
8 */\r
9\r
10( function() {\r
11 'use strict';\r
12\r
13 var TRISTATE_DISABLED = CKEDITOR.TRISTATE_DISABLED,\r
14 TRISTATE_OFF = CKEDITOR.TRISTATE_OFF;\r
15\r
16 CKEDITOR.plugins.add( 'indent', {\r
17 // jscs:disable maximumLineLength\r
1794320d 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,es-mx,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%\r
c63493c8
IB
19 // jscs:enable maximumLineLength\r
20 icons: 'indent,indent-rtl,outdent,outdent-rtl', // %REMOVE_LINE_CORE%\r
21 hidpi: true, // %REMOVE_LINE_CORE%\r
22\r
23 init: function( editor ) {\r
24 var genericDefinition = CKEDITOR.plugins.indent.genericDefinition;\r
25\r
26 // Register generic commands.\r
27 setupGenericListeners( editor, editor.addCommand( 'indent', new genericDefinition( true ) ) );\r
28 setupGenericListeners( editor, editor.addCommand( 'outdent', new genericDefinition() ) );\r
29\r
30 // Create and register toolbar button if possible.\r
31 if ( editor.ui.addButton ) {\r
32 editor.ui.addButton( 'Indent', {\r
33 label: editor.lang.indent.indent,\r
34 command: 'indent',\r
35 directional: true,\r
36 toolbar: 'indent,20'\r
37 } );\r
38\r
39 editor.ui.addButton( 'Outdent', {\r
40 label: editor.lang.indent.outdent,\r
41 command: 'outdent',\r
42 directional: true,\r
43 toolbar: 'indent,10'\r
44 } );\r
45 }\r
46\r
47 // Register dirChanged listener.\r
48 editor.on( 'dirChanged', function( evt ) {\r
49 var range = editor.createRange(),\r
50 dataNode = evt.data.node;\r
51\r
52 range.setStartBefore( dataNode );\r
53 range.setEndAfter( dataNode );\r
54\r
55 var walker = new CKEDITOR.dom.walker( range ),\r
56 node;\r
57\r
58 while ( ( node = walker.next() ) ) {\r
59 if ( node.type == CKEDITOR.NODE_ELEMENT ) {\r
60 // A child with the defined dir is to be ignored.\r
61 if ( !node.equals( dataNode ) && node.getDirection() ) {\r
62 range.setStartAfter( node );\r
63 walker = new CKEDITOR.dom.walker( range );\r
64 continue;\r
65 }\r
66\r
67 // Switch alignment classes.\r
68 var classes = editor.config.indentClasses;\r
69 if ( classes ) {\r
70 var suffix = ( evt.data.dir == 'ltr' ) ? [ '_rtl', '' ] : [ '', '_rtl' ];\r
71 for ( var i = 0; i < classes.length; i++ ) {\r
72 if ( node.hasClass( classes[ i ] + suffix[ 0 ] ) ) {\r
73 node.removeClass( classes[ i ] + suffix[ 0 ] );\r
74 node.addClass( classes[ i ] + suffix[ 1 ] );\r
75 }\r
76 }\r
77 }\r
78\r
79 // Switch the margins.\r
80 var marginLeft = node.getStyle( 'margin-right' ),\r
81 marginRight = node.getStyle( 'margin-left' );\r
82\r
83 marginLeft ? node.setStyle( 'margin-left', marginLeft ) : node.removeStyle( 'margin-left' );\r
84 marginRight ? node.setStyle( 'margin-right', marginRight ) : node.removeStyle( 'margin-right' );\r
85 }\r
86 }\r
87 } );\r
88 }\r
89 } );\r
90\r
91 /**\r
92 * Global command class definitions and global helpers.\r
93 *\r
94 * @class\r
95 * @singleton\r
96 */\r
97 CKEDITOR.plugins.indent = {\r
98 /**\r
99 * A base class for a generic command definition, responsible mainly for creating\r
100 * Increase Indent and Decrease Indent toolbar buttons as well as for refreshing\r
101 * UI states.\r
102 *\r
103 * Commands of this class do not perform any indentation by themselves. They\r
104 * delegate this job to content-specific indentation commands (i.e. indentlist).\r
105 *\r
106 * @class CKEDITOR.plugins.indent.genericDefinition\r
107 * @extends CKEDITOR.commandDefinition\r
108 * @param {CKEDITOR.editor} editor The editor instance this command will be\r
109 * applied to.\r
110 * @param {String} name The name of the command.\r
111 * @param {Boolean} [isIndent] Defines the command as indenting or outdenting.\r
112 */\r
113 genericDefinition: function( isIndent ) {\r
114 /**\r
115 * Determines whether the command belongs to the indentation family.\r
116 * Otherwise it is assumed to be an outdenting command.\r
117 *\r
118 * @readonly\r
119 * @property {Boolean} [=false]\r
120 */\r
121 this.isIndent = !!isIndent;\r
122\r
123 // Mimic naive startDisabled behavior for outdent.\r
124 this.startDisabled = !this.isIndent;\r
125 },\r
126\r
127 /**\r
128 * A base class for specific indentation command definitions responsible for\r
129 * handling a pre-defined set of elements i.e. indentlist for lists or\r
130 * indentblock for text block elements.\r
131 *\r
132 * Commands of this class perform indentation operations and modify the DOM structure.\r
133 * They listen for events fired by {@link CKEDITOR.plugins.indent.genericDefinition}\r
134 * and execute defined actions.\r
135 *\r
136 * **NOTE**: This is not an {@link CKEDITOR.command editor command}.\r
137 * Context-specific commands are internal, for indentation system only.\r
138 *\r
139 * @class CKEDITOR.plugins.indent.specificDefinition\r
140 * @param {CKEDITOR.editor} editor The editor instance this command will be\r
141 * applied to.\r
142 * @param {String} name The name of the command.\r
143 * @param {Boolean} [isIndent] Defines the command as indenting or outdenting.\r
144 */\r
145 specificDefinition: function( editor, name, isIndent ) {\r
146 this.name = name;\r
147 this.editor = editor;\r
148\r
149 /**\r
150 * An object of jobs handled by the command. Each job consists\r
151 * of two functions: `refresh` and `exec` as well as the execution priority.\r
152 *\r
153 * * The `refresh` function determines whether a job is doable for\r
154 * a particular context. These functions are executed in the\r
155 * order of priorities, one by one, for all plugins that registered\r
156 * jobs. As jobs are related to generic commands, refreshing\r
157 * occurs when the global command is firing the `refresh` event.\r
158 *\r
159 * **Note**: This function must return either {@link CKEDITOR#TRISTATE_DISABLED}\r
160 * or {@link CKEDITOR#TRISTATE_OFF}.\r
161 *\r
162 * * The `exec` function modifies the DOM if possible. Just like\r
163 * `refresh`, `exec` functions are executed in the order of priorities\r
164 * while the generic command is executed. This function is not executed\r
165 * if `refresh` for this job returned {@link CKEDITOR#TRISTATE_DISABLED}.\r
166 *\r
167 * **Note**: This function must return a Boolean value, indicating whether it\r
168 * was successful. If a job was successful, then no other jobs are being executed.\r
169 *\r
170 * Sample definition:\r
171 *\r
172 * command.jobs = {\r
173 * // Priority = 20.\r
174 * '20': {\r
175 * refresh( editor, path ) {\r
176 * if ( condition )\r
177 * return CKEDITOR.TRISTATE_OFF;\r
178 * else\r
179 * return CKEDITOR.TRISTATE_DISABLED;\r
180 * },\r
181 * exec( editor ) {\r
182 * // DOM modified! This was OK.\r
183 * return true;\r
184 * }\r
185 * },\r
186 * // Priority = 60. This job is done later.\r
187 * '60': {\r
188 * // Another job.\r
189 * }\r
190 * };\r
191 *\r
192 * For additional information, please check comments for\r
193 * the `setupGenericListeners` function.\r
194 *\r
195 * @readonly\r
196 * @property {Object} [={}]\r
197 */\r
198 this.jobs = {};\r
199\r
200 /**\r
201 * Determines whether the editor that the command belongs to has\r
202 * {@link CKEDITOR.config#enterMode config.enterMode} set to {@link CKEDITOR#ENTER_BR}.\r
203 *\r
204 * @readonly\r
205 * @see CKEDITOR.config#enterMode\r
206 * @property {Boolean} [=false]\r
207 */\r
208 this.enterBr = editor.config.enterMode == CKEDITOR.ENTER_BR;\r
209\r
210 /**\r
211 * Determines whether the command belongs to the indentation family.\r
212 * Otherwise it is assumed to be an outdenting command.\r
213 *\r
214 * @readonly\r
215 * @property {Boolean} [=false]\r
216 */\r
217 this.isIndent = !!isIndent;\r
218\r
219 /**\r
220 * The name of the global command related to this one.\r
221 *\r
222 * @readonly\r
223 */\r
224 this.relatedGlobal = isIndent ? 'indent' : 'outdent';\r
225\r
226 /**\r
227 * A keystroke associated with this command (*Tab* or *Shift+Tab*).\r
228 *\r
229 * @readonly\r
230 */\r
231 this.indentKey = isIndent ? 9 : CKEDITOR.SHIFT + 9;\r
232\r
233 /**\r
234 * Stores created markers for the command so they can eventually be\r
235 * purged after the `exec` function is run.\r
236 */\r
237 this.database = {};\r
238 },\r
239\r
240 /**\r
241 * Registers content-specific commands as a part of the indentation system\r
242 * directed by generic commands. Once a command is registered,\r
243 * it listens for events of a related generic command.\r
244 *\r
245 * CKEDITOR.plugins.indent.registerCommands( editor, {\r
246 * 'indentlist': new indentListCommand( editor, 'indentlist' ),\r
247 * 'outdentlist': new indentListCommand( editor, 'outdentlist' )\r
248 * } );\r
249 *\r
250 * Content-specific commands listen for the generic command's `exec` and\r
251 * try to execute their own jobs, one after another. If some execution is\r
252 * successful, `evt.data.done` is set so no more jobs (commands) are involved.\r
253 *\r
254 * Content-specific commands also listen for the generic command's `refresh`\r
255 * and fill the `evt.data.states` object with states of jobs. A generic command\r
256 * uses this data to determine its own state and to update the UI.\r
257 *\r
258 * @member CKEDITOR.plugins.indent\r
259 * @param {CKEDITOR.editor} editor The editor instance this command is\r
260 * applied to.\r
261 * @param {Object} commands An object of {@link CKEDITOR.command}.\r
262 */\r
263 registerCommands: function( editor, commands ) {\r
264 editor.on( 'pluginsLoaded', function() {\r
265 for ( var name in commands ) {\r
266 ( function( editor, command ) {\r
267 var relatedGlobal = editor.getCommand( command.relatedGlobal );\r
268\r
269 for ( var priority in command.jobs ) {\r
270 // Observe generic exec event and execute command when necessary.\r
271 // If the command was successfully handled by the command and\r
272 // DOM has been modified, stop event propagation so no other plugin\r
273 // will bother. Job is done.\r
274 relatedGlobal.on( 'exec', function( evt ) {\r
275 if ( evt.data.done )\r
276 return;\r
277\r
278 // Make sure that anything this command will do is invisible\r
279 // for undoManager. What undoManager only can see and\r
280 // remember is the execution of the global command (relatedGlobal).\r
281 editor.fire( 'lockSnapshot' );\r
282\r
283 if ( command.execJob( editor, priority ) )\r
284 evt.data.done = true;\r
285\r
286 editor.fire( 'unlockSnapshot' );\r
287\r
288 // Clean up the markers.\r
289 CKEDITOR.dom.element.clearAllMarkers( command.database );\r
290 }, this, null, priority );\r
291\r
292 // Observe generic refresh event and force command refresh.\r
293 // Once refreshed, save command state in event data\r
294 // so generic command plugin can update its own state and UI.\r
295 relatedGlobal.on( 'refresh', function( evt ) {\r
296 if ( !evt.data.states )\r
297 evt.data.states = {};\r
298\r
299 evt.data.states[ command.name + '@' + priority ] =\r
300 command.refreshJob( editor, priority, evt.data.path );\r
301 }, this, null, priority );\r
302 }\r
303\r
304 // Since specific indent commands have no UI elements,\r
305 // they need to be manually registered as a editor feature.\r
306 editor.addFeature( command );\r
307 } )( this, commands[ name ] );\r
308 }\r
309 } );\r
310 }\r
311 };\r
312\r
313 CKEDITOR.plugins.indent.genericDefinition.prototype = {\r
314 context: 'p',\r
315\r
316 exec: function() {}\r
317 };\r
318\r
319 CKEDITOR.plugins.indent.specificDefinition.prototype = {\r
320 /**\r
321 * Executes the content-specific procedure if the context is correct.\r
322 * It calls the `exec` function of a job of the given `priority`\r
323 * that modifies the DOM.\r
324 *\r
325 * @param {CKEDITOR.editor} editor The editor instance this command\r
326 * will be applied to.\r
327 * @param {Number} priority The priority of the job to be executed.\r
328 * @returns {Boolean} Indicates whether the job was successful.\r
329 */\r
330 execJob: function( editor, priority ) {\r
331 var job = this.jobs[ priority ];\r
332\r
333 if ( job.state != TRISTATE_DISABLED )\r
334 return job.exec.call( this, editor );\r
335 },\r
336\r
337 /**\r
338 * Calls the `refresh` function of a job of the given `priority`.\r
339 * The function returns the state of the job which can be either\r
340 * {@link CKEDITOR#TRISTATE_DISABLED} or {@link CKEDITOR#TRISTATE_OFF}.\r
341 *\r
342 * @param {CKEDITOR.editor} editor The editor instance this command\r
343 * will be applied to.\r
344 * @param {Number} priority The priority of the job to be executed.\r
345 * @returns {Number} The state of the job.\r
346 */\r
347 refreshJob: function( editor, priority, path ) {\r
348 var job = this.jobs[ priority ];\r
349\r
350 if ( !editor.activeFilter.checkFeature( this ) )\r
351 job.state = TRISTATE_DISABLED;\r
352 else\r
353 job.state = job.refresh.call( this, editor, path );\r
354\r
355 return job.state;\r
356 },\r
357\r
358 /**\r
359 * Checks if the element path contains the element handled\r
360 * by this indentation command.\r
361 *\r
362 * @param {CKEDITOR.dom.elementPath} node A path to be checked.\r
363 * @returns {CKEDITOR.dom.element}\r
364 */\r
365 getContext: function( path ) {\r
366 return path.contains( this.context );\r
367 }\r
368 };\r
369\r
370 /**\r
371 * Attaches event listeners for this generic command. Since the indentation\r
372 * system is event-oriented, generic commands communicate with\r
373 * content-specific commands using the `exec` and `refresh` events.\r
374 *\r
375 * Listener priorities are crucial. Different indentation phases\r
376 * are executed with different priorities.\r
377 *\r
378 * For the `exec` event:\r
379 *\r
380 * * 0: Selection and bookmarks are saved by the generic command.\r
381 * * 1-99: Content-specific commands try to indent the code by executing\r
382 * their own jobs ({@link CKEDITOR.plugins.indent.specificDefinition#jobs}).\r
383 * * 100: Bookmarks are re-selected by the generic command.\r
384 *\r
385 * The visual interpretation looks as follows:\r
386 *\r
387 * +------------------+\r
388 * | Exec event fired |\r
389 * +------ + ---------+\r
390 * |\r
391 * 0 -<----------+ Selection and bookmarks saved.\r
392 * |\r
393 * |\r
394 * 25 -<---+ Exec 1st job of plugin#1 (return false, continuing...).\r
395 * |\r
396 * |\r
397 * 50 -<---+ Exec 1st job of plugin#2 (return false, continuing...).\r
398 * |\r
399 * |\r
400 * 75 -<---+ Exec 2nd job of plugin#1 (only if plugin#2 failed).\r
401 * |\r
402 * |\r
403 * 100 -<-----------+ Re-select bookmarks, clean-up.\r
404 * |\r
405 * +-------- v ----------+\r
406 * | Exec event finished |\r
407 * +---------------------+\r
408 *\r
409 * For the `refresh` event:\r
410 *\r
411 * * <100: Content-specific commands refresh their job states according\r
412 * to the given path. Jobs save their states in the `evt.data.states` object\r
413 * passed along with the event. This can be either {@link CKEDITOR#TRISTATE_DISABLED}\r
414 * or {@link CKEDITOR#TRISTATE_OFF}.\r
415 * * 100: Command state is determined according to what states\r
416 * have been returned by content-specific jobs (`evt.data.states`).\r
417 * UI elements are updated at this stage.\r
418 *\r
419 * **Note**: If there is at least one job with the {@link CKEDITOR#TRISTATE_OFF} state,\r
420 * then the generic command state is also {@link CKEDITOR#TRISTATE_OFF}. Otherwise,\r
421 * the command state is {@link CKEDITOR#TRISTATE_DISABLED}.\r
422 *\r
423 * @param {CKEDITOR.command} command The command to be set up.\r
424 * @private\r
425 */\r
426 function setupGenericListeners( editor, command ) {\r
427 var selection, bookmarks;\r
428\r
429 // Set the command state according to content-specific\r
430 // command states.\r
431 command.on( 'refresh', function( evt ) {\r
432 // If no state comes with event data, disable command.\r
433 var states = [ TRISTATE_DISABLED ];\r
434\r
435 for ( var s in evt.data.states )\r
436 states.push( evt.data.states[ s ] );\r
437\r
438 this.setState( CKEDITOR.tools.search( states, TRISTATE_OFF ) ? TRISTATE_OFF : TRISTATE_DISABLED );\r
439 }, command, null, 100 );\r
440\r
441 // Initialization. Save bookmarks and mark event as not handled\r
442 // by any plugin (command) yet.\r
443 command.on( 'exec', function( evt ) {\r
444 selection = editor.getSelection();\r
445 bookmarks = selection.createBookmarks( 1 );\r
446\r
447 // Mark execution as not handled yet.\r
448 if ( !evt.data )\r
449 evt.data = {};\r
450\r
451 evt.data.done = false;\r
452 }, command, null, 0 );\r
453\r
454 // Housekeeping. Make sure selectionChange will be called.\r
455 // Also re-select previously saved bookmarks.\r
456 command.on( 'exec', function() {\r
457 editor.forceNextSelectionCheck();\r
458 selection.selectBookmarks( bookmarks );\r
459 }, command, null, 100 );\r
460 }\r
461} )();\r