]> git.immae.eu Git - perso/Immae/Projets/packagist/piedsjaloux-ckeditor-component.git/blob - sources/plugins/indentblock/plugin.js
Initial commit
[perso/Immae/Projets/packagist/piedsjaloux-ckeditor-component.git] / sources / plugins / indentblock / plugin.js
1 /**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6 /**
7 * @fileOverview Handles the indentation of block elements.
8 */
9
10 ( function() {
11 'use strict';
12
13 var $listItem = CKEDITOR.dtd.$listItem,
14 $list = CKEDITOR.dtd.$list,
15 TRISTATE_DISABLED = CKEDITOR.TRISTATE_DISABLED,
16 TRISTATE_OFF = CKEDITOR.TRISTATE_OFF;
17
18 CKEDITOR.plugins.add( 'indentblock', {
19 requires: 'indent',
20 init: function( editor ) {
21 var globalHelpers = CKEDITOR.plugins.indent,
22 classes = editor.config.indentClasses;
23
24 // Register commands.
25 globalHelpers.registerCommands( editor, {
26 indentblock: new commandDefinition( editor, 'indentblock', true ),
27 outdentblock: new commandDefinition( editor, 'outdentblock' )
28 } );
29
30 function commandDefinition() {
31 globalHelpers.specificDefinition.apply( this, arguments );
32
33 this.allowedContent = {
34 'div h1 h2 h3 h4 h5 h6 ol p pre ul': {
35 // Do not add elements, but only text-align style if element is validated by other rule.
36 propertiesOnly: true,
37 styles: !classes ? 'margin-left,margin-right' : null,
38 classes: classes || null
39 }
40 };
41
42 if ( this.enterBr )
43 this.allowedContent.div = true;
44
45 this.requiredContent = ( this.enterBr ? 'div' : 'p' ) +
46 ( classes ? '(' + classes.join( ',' ) + ')' : '{margin-left}' );
47
48 this.jobs = {
49 '20': {
50 refresh: function( editor, path ) {
51 var firstBlock = path.block || path.blockLimit;
52
53 // Switch context from somewhere inside list item to list item,
54 // if not found just assign self (doing nothing).
55 if ( !firstBlock.is( $listItem ) ) {
56 var ascendant = firstBlock.getAscendant( $listItem );
57
58 firstBlock = ( ascendant && path.contains( ascendant ) ) || firstBlock;
59 }
60
61 // Switch context from list item to list
62 // because indentblock can indent entire list
63 // but not a single list element.
64
65 if ( firstBlock.is( $listItem ) )
66 firstBlock = firstBlock.getParent();
67
68 // [-] Context in the path or ENTER_BR
69 //
70 // Don't try to indent if the element is out of
71 // this plugin's scope. This assertion is omitted
72 // if ENTER_BR is in use since there may be no block
73 // in the path.
74
75 if ( !this.enterBr && !this.getContext( path ) )
76 return TRISTATE_DISABLED;
77
78 else if ( classes ) {
79
80 // [+] Context in the path or ENTER_BR
81 // [+] IndentClasses
82 //
83 // If there are indentation classes, check if reached
84 // the highest level of indentation. If so, disable
85 // the command.
86
87 if ( indentClassLeft.call( this, firstBlock, classes ) )
88 return TRISTATE_OFF;
89 else
90 return TRISTATE_DISABLED;
91 } else {
92
93 // [+] Context in the path or ENTER_BR
94 // [-] IndentClasses
95 // [+] Indenting
96 //
97 // No indent-level limitations due to indent classes.
98 // Indent-like command can always be executed.
99
100 if ( this.isIndent )
101 return TRISTATE_OFF;
102
103 // [+] Context in the path or ENTER_BR
104 // [-] IndentClasses
105 // [-] Indenting
106 // [-] Block in the path
107 //
108 // No block in path. There's no element to apply indentation
109 // so disable the command.
110
111 else if ( !firstBlock )
112 return TRISTATE_DISABLED;
113
114 // [+] Context in the path or ENTER_BR
115 // [-] IndentClasses
116 // [-] Indenting
117 // [+] Block in path.
118 //
119 // Not using indentClasses but there is firstBlock.
120 // We can calculate current indentation level and
121 // try to increase/decrease it.
122
123 else {
124 return CKEDITOR[
125 ( getIndent( firstBlock ) || 0 ) <= 0 ? 'TRISTATE_DISABLED' : 'TRISTATE_OFF'
126 ];
127 }
128 }
129 },
130
131 exec: function( editor ) {
132 var selection = editor.getSelection(),
133 range = selection && selection.getRanges()[ 0 ],
134 nearestListBlock;
135
136 // If there's some list in the path, then it will be
137 // a full-list indent by increasing or decreasing margin property.
138 if ( ( nearestListBlock = editor.elementPath().contains( $list ) ) )
139 indentElement.call( this, nearestListBlock, classes );
140
141 // If no list in the path, use iterator to indent all the possible
142 // paragraphs in the range, creating them if necessary.
143 else {
144 var iterator = range.createIterator(),
145 enterMode = editor.config.enterMode,
146 block;
147
148 iterator.enforceRealBlocks = true;
149 iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR;
150
151 while ( ( block = iterator.getNextParagraph( enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ) ) ) {
152 if ( !block.isReadOnly() )
153 indentElement.call( this, block, classes );
154 }
155 }
156
157 return true;
158 }
159 }
160 };
161 }
162
163 CKEDITOR.tools.extend( commandDefinition.prototype, globalHelpers.specificDefinition.prototype, {
164 // Elements that, if in an elementpath, will be handled by this
165 // command. They restrict the scope of the plugin.
166 context: { div: 1, dl: 1, h1: 1, h2: 1, h3: 1, h4: 1, h5: 1, h6: 1, ul: 1, ol: 1, p: 1, pre: 1, table: 1 },
167
168 // A regex built on config#indentClasses to detect whether an
169 // element has some indentClass or not.
170 classNameRegex: classes ? new RegExp( '(?:^|\\s+)(' + classes.join( '|' ) + ')(?=$|\\s)' ) : null
171 } );
172 }
173 } );
174
175 // Generic indentation procedure for indentation of any element
176 // either with margin property or config#indentClass.
177 function indentElement( element, classes, dir ) {
178 if ( element.getCustomData( 'indent_processed' ) )
179 return;
180
181 var editor = this.editor,
182 isIndent = this.isIndent;
183
184 if ( classes ) {
185 // Transform current class f to indent step index.
186 var indentClass = element.$.className.match( this.classNameRegex ),
187 indentStep = 0;
188
189 if ( indentClass ) {
190 indentClass = indentClass[ 1 ];
191 indentStep = CKEDITOR.tools.indexOf( classes, indentClass ) + 1;
192 }
193
194 // Operate on indent step index, transform indent step index
195 // back to class name.
196 if ( ( indentStep += isIndent ? 1 : -1 ) < 0 )
197 return;
198
199 indentStep = Math.min( indentStep, classes.length );
200 indentStep = Math.max( indentStep, 0 );
201 element.$.className = CKEDITOR.tools.ltrim( element.$.className.replace( this.classNameRegex, '' ) );
202
203 if ( indentStep > 0 )
204 element.addClass( classes[ indentStep - 1 ] );
205 } else {
206 var indentCssProperty = getIndentCss( element, dir ),
207 currentOffset = parseInt( element.getStyle( indentCssProperty ), 10 ),
208 indentOffset = editor.config.indentOffset || 40;
209
210 if ( isNaN( currentOffset ) )
211 currentOffset = 0;
212
213 currentOffset += ( isIndent ? 1 : -1 ) * indentOffset;
214
215 if ( currentOffset < 0 )
216 return;
217
218 currentOffset = Math.max( currentOffset, 0 );
219 currentOffset = Math.ceil( currentOffset / indentOffset ) * indentOffset;
220
221 element.setStyle(
222 indentCssProperty,
223 currentOffset ? currentOffset + ( editor.config.indentUnit || 'px' ) : ''
224 );
225
226 if ( element.getAttribute( 'style' ) === '' )
227 element.removeAttribute( 'style' );
228 }
229
230 CKEDITOR.dom.element.setMarker( this.database, element, 'indent_processed', 1 );
231
232 return;
233 }
234
235 // Method that checks if current indentation level for an element
236 // reached the limit determined by config#indentClasses.
237 function indentClassLeft( node, classes ) {
238 var indentClass = node.$.className.match( this.classNameRegex ),
239 isIndent = this.isIndent;
240
241 // If node has one of the indentClasses:
242 // * If it holds the topmost indentClass, then
243 // no more classes have left.
244 // * If it holds any other indentClass, it can use the next one
245 // or the previous one.
246 // * Outdent is always possible. We can remove indentClass.
247 if ( indentClass )
248 return isIndent ? indentClass[ 1 ] != classes.slice( -1 ) : true;
249
250 // If node has no class which belongs to indentClasses,
251 // then it is at 0-level. It can be indented but not outdented.
252 else
253 return isIndent;
254 }
255
256 // Determines indent CSS property for an element according to
257 // what is the direction of such element. It can be either `margin-left`
258 // or `margin-right`.
259 function getIndentCss( element, dir ) {
260 return ( dir || element.getComputedStyle( 'direction' ) ) == 'ltr' ? 'margin-left' : 'margin-right';
261 }
262
263 // Return the numerical indent value of margin-left|right of an element,
264 // considering element's direction. If element has no margin specified,
265 // NaN is returned.
266 function getIndent( element ) {
267 return parseInt( element.getStyle( getIndentCss( element ) ), 10 );
268 }
269 } )();
270
271 /**
272 * A list of classes to use for indenting the contents. If set to `null`, no classes will be used
273 * and instead the {@link #indentUnit} and {@link #indentOffset} properties will be used.
274 *
275 * // Use the 'Indent1', 'Indent2', 'Indent3' classes.
276 * config.indentClasses = ['Indent1', 'Indent2', 'Indent3'];
277 *
278 * @cfg {Array} [indentClasses=null]
279 * @member CKEDITOR.config
280 */
281
282 /**
283 * The size in {@link CKEDITOR.config#indentUnit indentation units} of each indentation step.
284 *
285 * config.indentOffset = 4;
286 *
287 * @cfg {Number} [indentOffset=40]
288 * @member CKEDITOR.config
289 */
290
291 /**
292 * The unit used for {@link CKEDITOR.config#indentOffset indentation offset}.
293 *
294 * config.indentUnit = 'em';
295 *
296 * @cfg {String} [indentUnit='px']
297 * @member CKEDITOR.config
298 */