indentblock.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module indent/indentblock
  7. */
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import IndentBlockCommand from './indentblockcommand';
  10. import IndentUsingOffset from './indentcommandbehavior/indentusingoffset';
  11. import IndentUsingClasses from './indentcommandbehavior/indentusingclasses';
  12. /**
  13. * The block indentation feature.
  14. *
  15. * It registers the `'indentBlock'` and `'outdentBlock'` commands.
  16. *
  17. * If the plugin {@link module:indent/indent~Indent} is defined, it also attaches the `'indentBlock'` and `'outdentBlock'` commands to
  18. * the `'indent'` and `'outdent'` commands.
  19. *
  20. * @extends module:core/plugin~Plugin
  21. */
  22. export default class IndentBlock extends Plugin {
  23. /**
  24. * @inheritDoc
  25. */
  26. constructor( editor ) {
  27. super( editor );
  28. editor.config.define( 'indentBlock', {
  29. offset: 40,
  30. unit: 'px'
  31. } );
  32. }
  33. /**
  34. * @inheritDoc
  35. */
  36. static get pluginName() {
  37. return 'IndentBlock';
  38. }
  39. /**
  40. * @inheritDoc
  41. */
  42. init() {
  43. const editor = this.editor;
  44. const schema = editor.model.schema;
  45. const conversion = editor.conversion;
  46. const configuration = editor.config.get( 'indentBlock' );
  47. // Enable block indentation by default in paragraph and default headings.
  48. const knownElements = [ 'paragraph', 'heading1', 'heading2', 'heading3', 'heading4', 'heading5', 'heading6' ];
  49. knownElements.forEach( elementName => {
  50. if ( schema.isRegistered( elementName ) ) {
  51. schema.extend( elementName, { allowAttributes: 'blockIndent' } );
  52. }
  53. } );
  54. const useOffsetConfig = !configuration.classes || !configuration.classes.length;
  55. const indentConfig = Object.assign( { direction: 'forward' }, configuration );
  56. const outdentConfig = Object.assign( { direction: 'backward' }, configuration );
  57. if ( useOffsetConfig ) {
  58. this._setupConversionUsingOffset( conversion );
  59. editor.commands.add( 'indentBlock', new IndentBlockCommand( editor, new IndentUsingOffset( indentConfig ) ) );
  60. editor.commands.add( 'outdentBlock', new IndentBlockCommand( editor, new IndentUsingOffset( outdentConfig ) ) );
  61. } else {
  62. this._setupConversionUsingClasses( configuration.classes );
  63. editor.commands.add( 'indentBlock', new IndentBlockCommand( editor, new IndentUsingClasses( indentConfig ) ) );
  64. editor.commands.add( 'outdentBlock', new IndentBlockCommand( editor, new IndentUsingClasses( outdentConfig ) ) );
  65. }
  66. }
  67. /**
  68. * @inheritDoc
  69. */
  70. afterInit() {
  71. const editor = this.editor;
  72. const indentCommand = editor.commands.get( 'indent' );
  73. const outdentCommand = editor.commands.get( 'outdent' );
  74. indentCommand.registerChildCommand( editor.commands.get( 'indentBlock' ) );
  75. outdentCommand.registerChildCommand( editor.commands.get( 'outdentBlock' ) );
  76. }
  77. /**
  78. * Setups conversion for using offset indents.
  79. *
  80. * @private
  81. */
  82. _setupConversionUsingOffset() {
  83. const conversion = this.editor.conversion;
  84. const locale = this.editor.locale;
  85. const marginProperty = locale.contentLanguageDirection === 'rtl' ? 'margin-right' : 'margin-left';
  86. conversion.for( 'upcast' ).attributeToAttribute( {
  87. view: {
  88. styles: {
  89. [ marginProperty ]: /[\s\S]+/
  90. }
  91. },
  92. model: {
  93. key: 'blockIndent',
  94. value: viewElement => viewElement.getStyle( marginProperty )
  95. }
  96. } );
  97. // The margin shorthand should also work.
  98. conversion.for( 'upcast' ).attributeToAttribute( {
  99. view: {
  100. styles: {
  101. 'margin': /[\s\S]+/
  102. }
  103. },
  104. model: {
  105. key: 'blockIndent',
  106. value: viewElement => normalizeToMarginSideStyle( viewElement.getStyle( 'margin' ), marginProperty )
  107. }
  108. } );
  109. conversion.for( 'downcast' ).attributeToAttribute( {
  110. model: 'blockIndent',
  111. view: modelAttributeValue => {
  112. return {
  113. key: 'style',
  114. value: {
  115. [ marginProperty ]: modelAttributeValue
  116. }
  117. };
  118. }
  119. } );
  120. }
  121. /**
  122. * Setups conversion for using classes.
  123. *
  124. * @param {Array.<String>} classes
  125. * @private
  126. */
  127. _setupConversionUsingClasses( classes ) {
  128. const definition = {
  129. model: {
  130. key: 'blockIndent',
  131. values: []
  132. },
  133. view: {}
  134. };
  135. for ( const className of classes ) {
  136. definition.model.values.push( className );
  137. definition.view[ className ] = {
  138. key: 'class',
  139. value: [ className ]
  140. };
  141. }
  142. this.editor.conversion.attributeToAttribute( definition );
  143. }
  144. }
  145. // Normalizes the margin shorthand value to the value of margin-left or margin-right CSS property.
  146. //
  147. // As such it will return:
  148. //
  149. // - '1em' -> '1em'
  150. // - '2px 1em' -> '1em'
  151. // - '2px 1em 3px' -> '1em'
  152. // - '2px 10px 3px 1em'
  153. // -> '1em' (side "margin-left")
  154. // -> '10px' (side "margin-right")
  155. //
  156. // @param {String} marginStyleValue Margin style value.
  157. // @param {String} side "margin-left" or "margin-right" depending on which margin should be returned.
  158. // @returns {String} Extracted value of margin-left or margin-right.
  159. function normalizeToMarginSideStyle( marginStyleValue, side ) {
  160. // Splits the margin shorthand, ie margin: 2em 4em.
  161. const marginEntries = marginStyleValue.split( ' ' );
  162. let marginValue;
  163. // If only one value defined, ie: `margin: 1px`.
  164. marginValue = marginEntries[ 0 ];
  165. // If only two values defined, ie: `margin: 1px 2px`.
  166. if ( marginEntries[ 1 ] ) {
  167. marginValue = marginEntries[ 1 ];
  168. }
  169. // If four values defined, ie: `margin: 1px 2px 3px 4px`.
  170. if ( marginEntries[ 3 ] ) {
  171. if ( side === 'margin-left' ) {
  172. marginValue = marginEntries[ 3 ];
  173. } else {
  174. marginValue = marginEntries[ 1 ];
  175. }
  176. }
  177. return marginValue;
  178. }
  179. /**
  180. * The configuration of the {@link module:indent/indentblock~IndentBlock block indentation feature}.
  181. *
  182. * Read more in {@link module:indent/indentblock~IndentBlockConfig}.
  183. *
  184. * @member {module:indent/indentblock~IndentBlockConfig} module:core/editor/editorconfig~EditorConfig#indentBlock
  185. */
  186. /**
  187. * The configuration of the block indentation feature.
  188. *
  189. * If no {@link module:indent/indentblock~IndentBlockConfig#classes} are set, the block indentation feature will use
  190. * {@link module:indent/indentblock~IndentBlockConfig#offset} and {@link module:indent/indentblock~IndentBlockConfig#unit} to
  191. * create indentation steps.
  192. *
  193. * ClassicEditor
  194. * .create( editorElement, {
  195. * indentBlock: {
  196. * offset: 2,
  197. * unit: 'em'
  198. * }
  199. * } )
  200. * .then( ... )
  201. * .catch( ... );
  202. *
  203. * Alternatively, the block indentation feature may set one of defined {@link module:indent/indentblock~IndentBlockConfig#classes} as
  204. * indentation steps:
  205. *
  206. * ClassicEditor
  207. * .create( editorElement, {
  208. * indentBlock: {
  209. * classes: [
  210. * 'indent-a', // The first step - smallest indentation.
  211. * 'indent-b',
  212. * 'indent-c',
  213. * 'indent-d',
  214. * 'indent-e' // The last step - biggest indentation.
  215. * ]
  216. * }
  217. * } )
  218. * .then( ... )
  219. * .catch( ... );
  220. *
  221. * In the example above only 5 indentation steps will be available.
  222. *
  223. * See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
  224. *
  225. * @interface IndentBlockConfig
  226. */
  227. /**
  228. * The size of indentation {@link module:indent/indentblock~IndentBlockConfig#unit units} for each indentation step.
  229. *
  230. * @default 40
  231. * @member {Number} module:indent/indentblock~IndentBlockConfig#offset
  232. */
  233. /**
  234. * The unit used for indentation {@link module:indent/indentblock~IndentBlockConfig#offset}.
  235. *
  236. * @default 'px'
  237. * @member {String} module:indent/indentblock~IndentBlockConfig#unit
  238. */
  239. /**
  240. * An optional list of classes to use for indenting the editor content. If not set or set to an empty array, no classes will be used.
  241. * The {@link module:indent/indentblock~IndentBlockConfig#unit `indentBlock.unit`} and
  242. * {@link module:indent/indentblock~IndentBlockConfig#offset `indentBlock.offset`} properties will be used instead.
  243. *
  244. * @default undefined
  245. * @member {Array.<String>|undefined} module:indent/indentblock~IndentBlockConfig#classes
  246. */