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