diff options
Diffstat (limited to 'sources/plugins/indent/plugin.js')
-rw-r--r-- | sources/plugins/indent/plugin.js | 461 |
1 files changed, 461 insertions, 0 deletions
diff --git a/sources/plugins/indent/plugin.js b/sources/plugins/indent/plugin.js new file mode 100644 index 0000000..0e887cf --- /dev/null +++ b/sources/plugins/indent/plugin.js | |||
@@ -0,0 +1,461 @@ | |||
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 | } )(); | ||