plugin.js 9.6 KB

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