plugin.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @fileOverview Justify commands.
  7. */
  8. ( function() {
  9. function getAlignment( element, useComputedState ) {
  10. useComputedState = useComputedState === undefined || useComputedState;
  11. var align;
  12. if ( useComputedState )
  13. align = element.getComputedStyle( 'text-align' );
  14. else {
  15. while ( !element.hasAttribute || !( element.hasAttribute( 'align' ) || element.getStyle( 'text-align' ) ) ) {
  16. var parent = element.getParent();
  17. if ( !parent )
  18. break;
  19. element = parent;
  20. }
  21. align = element.getStyle( 'text-align' ) || element.getAttribute( 'align' ) || '';
  22. }
  23. // Sometimes computed values doesn't tell.
  24. align && ( align = align.replace( /(?:-(?:moz|webkit)-)?(?:start|auto)/i, '' ) );
  25. !align && useComputedState && ( align = element.getComputedStyle( 'direction' ) == 'rtl' ? 'right' : 'left' );
  26. return align;
  27. }
  28. function justifyCommand( editor, name, value ) {
  29. this.editor = editor;
  30. this.name = name;
  31. this.value = value;
  32. this.context = 'p';
  33. var classes = editor.config.justifyClasses,
  34. blockTag = editor.config.enterMode == CKEDITOR.ENTER_P ? 'p' : 'div';
  35. if ( classes ) {
  36. switch ( value ) {
  37. case 'left':
  38. this.cssClassName = classes[ 0 ];
  39. break;
  40. case 'center':
  41. this.cssClassName = classes[ 1 ];
  42. break;
  43. case 'right':
  44. this.cssClassName = classes[ 2 ];
  45. break;
  46. case 'justify':
  47. this.cssClassName = classes[ 3 ];
  48. break;
  49. }
  50. this.cssClassRegex = new RegExp( '(?:^|\\s+)(?:' + classes.join( '|' ) + ')(?=$|\\s)' );
  51. this.requiredContent = blockTag + '(' + this.cssClassName + ')';
  52. }
  53. else {
  54. this.requiredContent = blockTag + '{text-align}';
  55. }
  56. this.allowedContent = {
  57. 'caption div h1 h2 h3 h4 h5 h6 p pre td th li': {
  58. // Do not add elements, but only text-align style if element is validated by other rule.
  59. propertiesOnly: true,
  60. styles: this.cssClassName ? null : 'text-align',
  61. classes: this.cssClassName || null
  62. }
  63. };
  64. // In enter mode BR we need to allow here for div, because when non other
  65. // feature allows div justify is the only plugin that uses it.
  66. if ( editor.config.enterMode == CKEDITOR.ENTER_BR )
  67. this.allowedContent.div = true;
  68. }
  69. function onDirChanged( e ) {
  70. var editor = e.editor;
  71. var range = editor.createRange();
  72. range.setStartBefore( e.data.node );
  73. range.setEndAfter( e.data.node );
  74. var walker = new CKEDITOR.dom.walker( range ),
  75. node;
  76. while ( ( node = walker.next() ) ) {
  77. if ( node.type == CKEDITOR.NODE_ELEMENT ) {
  78. // A child with the defined dir is to be ignored.
  79. if ( !node.equals( e.data.node ) && node.getDirection() ) {
  80. range.setStartAfter( node );
  81. walker = new CKEDITOR.dom.walker( range );
  82. continue;
  83. }
  84. // Switch the alignment.
  85. var classes = editor.config.justifyClasses;
  86. if ( classes ) {
  87. // The left align class.
  88. if ( node.hasClass( classes[ 0 ] ) ) {
  89. node.removeClass( classes[ 0 ] );
  90. node.addClass( classes[ 2 ] );
  91. }
  92. // The right align class.
  93. else if ( node.hasClass( classes[ 2 ] ) ) {
  94. node.removeClass( classes[ 2 ] );
  95. node.addClass( classes[ 0 ] );
  96. }
  97. }
  98. // Always switch CSS margins.
  99. var style = 'text-align';
  100. var align = node.getStyle( style );
  101. if ( align == 'left' )
  102. node.setStyle( style, 'right' );
  103. else if ( align == 'right' )
  104. node.setStyle( style, 'left' );
  105. }
  106. }
  107. }
  108. justifyCommand.prototype = {
  109. exec: function( editor ) {
  110. var selection = editor.getSelection(),
  111. enterMode = editor.config.enterMode;
  112. if ( !selection )
  113. return;
  114. var bookmarks = selection.createBookmarks(),
  115. ranges = selection.getRanges();
  116. var cssClassName = this.cssClassName,
  117. iterator, block;
  118. var useComputedState = editor.config.useComputedState;
  119. useComputedState = useComputedState === undefined || useComputedState;
  120. for ( var i = ranges.length - 1; i >= 0; i-- ) {
  121. iterator = ranges[ i ].createIterator();
  122. iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR;
  123. while ( ( block = iterator.getNextParagraph( enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ) ) ) {
  124. if ( block.isReadOnly() )
  125. continue;
  126. // Check if style or class might be applied to currently processed element (#455).
  127. var tag = block.getName(),
  128. isAllowedTextAlign, isAllowedCssClass;
  129. isAllowedTextAlign = editor.activeFilter.check( tag + '{text-align}' );
  130. isAllowedCssClass = editor.activeFilter.check( tag + '(' + cssClassName + ')' );
  131. if ( !isAllowedCssClass && !isAllowedTextAlign ) {
  132. continue;
  133. }
  134. block.removeAttribute( 'align' );
  135. block.removeStyle( 'text-align' );
  136. // Remove any of the alignment classes from the className.
  137. var className = cssClassName && ( block.$.className = CKEDITOR.tools.ltrim( block.$.className.replace( this.cssClassRegex, '' ) ) );
  138. var apply = ( this.state == CKEDITOR.TRISTATE_OFF ) && ( !useComputedState || ( getAlignment( block, true ) != this.value ) );
  139. if ( cssClassName && isAllowedCssClass ) {
  140. // Append the desired class name.
  141. if ( apply )
  142. block.addClass( cssClassName );
  143. else if ( !className )
  144. block.removeAttribute( 'class' );
  145. } else if ( apply && isAllowedTextAlign ) {
  146. block.setStyle( 'text-align', this.value );
  147. }
  148. }
  149. }
  150. editor.focus();
  151. editor.forceNextSelectionCheck();
  152. selection.selectBookmarks( bookmarks );
  153. },
  154. refresh: function( editor, path ) {
  155. var firstBlock = path.block || path.blockLimit,
  156. name = firstBlock.getName(),
  157. isEditable = firstBlock.equals( editor.editable() ),
  158. isStylable = this.cssClassName ? editor.activeFilter.check( name + '(' + this.cssClassName + ')' ) :
  159. editor.activeFilter.check( name + '{text-align}' );
  160. // #455
  161. // 1. Check if we are directly in editbale. Justification should be always allowed, and not highlighted.
  162. // Checking situation `body > ul` where ul is selected and path.blockLimit returns editable.
  163. // 2. Check if current element can have applied specific class.
  164. // 3. Check if current element can have applied text-align style.
  165. if ( isEditable && !CKEDITOR.dtd.$list[ path.lastElement.getName() ] ) {
  166. this.setState( CKEDITOR.TRISTATE_OFF );
  167. } else if ( !isEditable && isStylable ) {
  168. // 2 & 3 in one condition.
  169. this.setState( getAlignment( firstBlock, this.editor.config.useComputedState ) == this.value ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF );
  170. } else {
  171. this.setState( CKEDITOR.TRISTATE_DISABLED );
  172. }
  173. }
  174. };
  175. CKEDITOR.plugins.add( 'justify', {
  176. icons: 'justifyblock,justifycenter,justifyleft,justifyright', // %REMOVE_LINE_CORE%
  177. hidpi: true, // %REMOVE_LINE_CORE%
  178. init: function( editor ) {
  179. if ( editor.blockless )
  180. return;
  181. var left = new justifyCommand( editor, 'justifyleft', 'left' ),
  182. center = new justifyCommand( editor, 'justifycenter', 'center' ),
  183. right = new justifyCommand( editor, 'justifyright', 'right' ),
  184. justify = new justifyCommand( editor, 'justifyblock', 'justify' );
  185. editor.addCommand( 'justifyleft', left );
  186. editor.addCommand( 'justifycenter', center );
  187. editor.addCommand( 'justifyright', right );
  188. editor.addCommand( 'justifyblock', justify );
  189. if ( editor.ui.addButton ) {
  190. editor.ui.addButton( 'JustifyLeft', {
  191. label: editor.lang.common.alignLeft,
  192. command: 'justifyleft',
  193. toolbar: 'align,10'
  194. } );
  195. editor.ui.addButton( 'JustifyCenter', {
  196. label: editor.lang.common.center,
  197. command: 'justifycenter',
  198. toolbar: 'align,20'
  199. } );
  200. editor.ui.addButton( 'JustifyRight', {
  201. label: editor.lang.common.alignRight,
  202. command: 'justifyright',
  203. toolbar: 'align,30'
  204. } );
  205. editor.ui.addButton( 'JustifyBlock', {
  206. label: editor.lang.common.justify,
  207. command: 'justifyblock',
  208. toolbar: 'align,40'
  209. } );
  210. }
  211. editor.on( 'dirChanged', onDirChanged );
  212. }
  213. } );
  214. } )();
  215. /**
  216. * List of classes to use for aligning the contents. If it's `null`, no classes will be used
  217. * and instead the corresponding CSS values will be used.
  218. *
  219. * The array should contain 4 members, in the following order: left, center, right, justify.
  220. *
  221. * // Use the classes 'AlignLeft', 'AlignCenter', 'AlignRight', 'AlignJustify'
  222. * config.justifyClasses = [ 'AlignLeft', 'AlignCenter', 'AlignRight', 'AlignJustify' ];
  223. *
  224. * @cfg {Array} [justifyClasses=null]
  225. * @member CKEDITOR.config
  226. */