]>
Commit | Line | Data |
---|---|---|
7adcb81e IB |
1 | /**\r |
2 | * @license Copyright (c) 2003-2015, 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 | |
18 | lang: 'af,ar,bg,bn,bs,ca,cs,cy,da,de,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%\r | |
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 |