autoformat.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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. * @module autoformat/autoformat
  7. */
  8. import BlockAutoformatEditing from './blockautoformatediting';
  9. import InlineAutoformatEditing from './inlineautoformatediting';
  10. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  11. /**
  12. * Enables a set of predefined autoformatting actions.
  13. *
  14. * For a detailed overview, check the {@glink features/autoformat Autoformatting feature documentation}
  15. * and the {@glink api/autoformat package page}.
  16. *
  17. * @extends module:core/plugin~Plugin
  18. */
  19. export default class Autoformat extends Plugin {
  20. /**
  21. * @inheritDoc
  22. */
  23. static get pluginName() {
  24. return 'Autoformat';
  25. }
  26. /**
  27. * @inheritDoc
  28. */
  29. afterInit() {
  30. this._addListAutoformats();
  31. this._addBasicStylesAutoformats();
  32. this._addHeadingAutoformats();
  33. this._addBlockQuoteAutoformats();
  34. this._addCodeBlockAutoformats();
  35. }
  36. /**
  37. * Adds autoformatting related to the {@link module:list/list~List}.
  38. *
  39. * When typed:
  40. * - `* ` or `- ` – A paragraph will be changed to a bulleted list.
  41. * - `1. ` or `1) ` – A paragraph will be changed to a numbered list ("1" can be any digit or a list of digits).
  42. *
  43. * @private
  44. */
  45. _addListAutoformats() {
  46. const commands = this.editor.commands;
  47. if ( commands.get( 'bulletedList' ) ) {
  48. // eslint-disable-next-line no-new
  49. new BlockAutoformatEditing( this.editor, /^[*-]\s$/, 'bulletedList' );
  50. }
  51. if ( commands.get( 'numberedList' ) ) {
  52. // eslint-disable-next-line no-new
  53. new BlockAutoformatEditing( this.editor, /^1[.|)]\s$/, 'numberedList' );
  54. }
  55. }
  56. /**
  57. * Adds autoformatting related to the {@link module:basic-styles/bold~Bold},
  58. * {@link module:basic-styles/italic~Italic}, {@link module:basic-styles/code~Code}
  59. * and {@link module:basic-styles/strikethrough~Strikethrough}
  60. *
  61. * When typed:
  62. * - `**foobar**` – `**` characters are removed and `foobar` is set to bold,
  63. * - `__foobar__` – `__` characters are removed and `foobar` is set to bold,
  64. * - `*foobar*` – `*` characters are removed and `foobar` is set to italic,
  65. * - `_foobar_` – `_` characters are removed and `foobar` is set to italic,
  66. * - ``` `foobar` – ``` ` ``` characters are removed and `foobar` is set to code,
  67. * - `~~foobar~~` – `~~` characters are removed and `foobar` is set to strikethrough.
  68. *
  69. * @private
  70. */
  71. _addBasicStylesAutoformats() {
  72. const commands = this.editor.commands;
  73. if ( commands.get( 'bold' ) ) {
  74. /* eslint-disable no-new */
  75. const boldCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'bold' );
  76. new InlineAutoformatEditing( this.editor, /(\*\*)([^*]+)(\*\*)$/g, boldCallback );
  77. new InlineAutoformatEditing( this.editor, /(__)([^_]+)(__)$/g, boldCallback );
  78. /* eslint-enable no-new */
  79. }
  80. if ( commands.get( 'italic' ) ) {
  81. /* eslint-disable no-new */
  82. const italicCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'italic' );
  83. // The italic autoformatter cannot be triggered by the bold markers, so we need to check the
  84. // text before the pattern (e.g. `(?:^|[^\*])`).
  85. new InlineAutoformatEditing( this.editor, /(?:^|[^*])(\*)([^*_]+)(\*)$/g, italicCallback );
  86. new InlineAutoformatEditing( this.editor, /(?:^|[^_])(_)([^_]+)(_)$/g, italicCallback );
  87. /* eslint-enable no-new */
  88. }
  89. if ( commands.get( 'code' ) ) {
  90. /* eslint-disable no-new */
  91. const codeCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'code' );
  92. new InlineAutoformatEditing( this.editor, /(`)([^`]+)(`)$/g, codeCallback );
  93. /* eslint-enable no-new */
  94. }
  95. if ( commands.get( 'strikethrough' ) ) {
  96. /* eslint-disable no-new */
  97. const strikethroughCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'strikethrough' );
  98. new InlineAutoformatEditing( this.editor, /(~~)([^~]+)(~~)$/g, strikethroughCallback );
  99. /* eslint-enable no-new */
  100. }
  101. }
  102. /**
  103. * Adds autoformatting related to {@link module:heading/heading~Heading}.
  104. *
  105. * It is using a number at the end of the command name to associate it with the proper trigger:
  106. *
  107. * * `heading` with value `heading1` will be executed when typing `#`,
  108. * * `heading` with value `heading2` will be executed when typing `##`,
  109. * * ... up to `heading6` and `######`.
  110. *
  111. * @private
  112. */
  113. _addHeadingAutoformats() {
  114. const command = this.editor.commands.get( 'heading' );
  115. if ( command ) {
  116. command.modelElements
  117. .filter( name => name.match( /^heading[1-6]$/ ) )
  118. .forEach( commandValue => {
  119. const level = commandValue[ 7 ];
  120. const pattern = new RegExp( `^(#{${ level }})\\s$` );
  121. // eslint-disable-next-line no-new
  122. new BlockAutoformatEditing( this.editor, pattern, () => {
  123. if ( !command.isEnabled ) {
  124. return false;
  125. }
  126. this.editor.execute( 'heading', { value: commandValue } );
  127. } );
  128. } );
  129. }
  130. }
  131. /**
  132. * Adds autoformatting related to {@link module:block-quote/blockquote~BlockQuote}.
  133. *
  134. * When typed:
  135. * * `> ` – A paragraph will be changed to a block quote.
  136. *
  137. * @private
  138. */
  139. _addBlockQuoteAutoformats() {
  140. if ( this.editor.commands.get( 'blockQuote' ) ) {
  141. // eslint-disable-next-line no-new
  142. new BlockAutoformatEditing( this.editor, /^>\s$/, 'blockQuote' );
  143. }
  144. }
  145. /**
  146. * Adds autoformatting related to {@link module:code-block/codeblock~CodeBlock}.
  147. *
  148. * When typed:
  149. * - `` ``` `` – A paragraph will be changed to a code block.
  150. *
  151. * @private
  152. */
  153. _addCodeBlockAutoformats() {
  154. if ( this.editor.commands.get( 'codeBlock' ) ) {
  155. // eslint-disable-next-line no-new
  156. new BlockAutoformatEditing( this.editor, /^```$/, 'codeBlock' );
  157. }
  158. }
  159. }
  160. // Helper function for getting `InlineAutoformatEditing` callbacks that checks if command is enabled.
  161. //
  162. // @param {module:core/editor/editor~Editor} editor
  163. // @param {String} attributeKey
  164. // @returns {Function}
  165. function getCallbackFunctionForInlineAutoformat( editor, attributeKey ) {
  166. return ( writer, rangesToFormat ) => {
  167. const command = editor.commands.get( attributeKey );
  168. if ( !command.isEnabled ) {
  169. return false;
  170. }
  171. const validRanges = editor.model.schema.getValidRanges( rangesToFormat, attributeKey );
  172. for ( const range of validRanges ) {
  173. writer.setAttribute( attributeKey, true, range );
  174. }
  175. // After applying attribute to the text, remove given attribute from the selection.
  176. // This way user is able to type a text without attribute used by auto formatter.
  177. writer.removeSelectionAttribute( attributeKey );
  178. };
  179. }