autoformat.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  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. blockAutoformatEditing( this.editor, this, /^[*-]\s$/, 'bulletedList' );
  49. }
  50. if ( commands.get( 'numberedList' ) ) {
  51. blockAutoformatEditing( this.editor, this, /^1[.|)]\s$/, 'numberedList' );
  52. }
  53. }
  54. /**
  55. * Adds autoformatting related to the {@link module:basic-styles/bold~Bold},
  56. * {@link module:basic-styles/italic~Italic}, {@link module:basic-styles/code~Code}
  57. * and {@link module:basic-styles/strikethrough~Strikethrough}
  58. *
  59. * When typed:
  60. * - `**foobar**` – `**` characters are removed and `foobar` is set to bold,
  61. * - `__foobar__` – `__` characters are removed and `foobar` is set to bold,
  62. * - `*foobar*` – `*` characters are removed and `foobar` is set to italic,
  63. * - `_foobar_` – `_` characters are removed and `foobar` is set to italic,
  64. * - ``` `foobar` – ``` ` ``` characters are removed and `foobar` is set to code,
  65. * - `~~foobar~~` – `~~` characters are removed and `foobar` is set to strikethrough.
  66. *
  67. * @private
  68. */
  69. _addBasicStylesAutoformats() {
  70. const commands = this.editor.commands;
  71. if ( commands.get( 'bold' ) ) {
  72. const boldCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'bold' );
  73. inlineAutoformatEditing( this.editor, this, /(\*\*)([^*]+)(\*\*)$/g, boldCallback );
  74. inlineAutoformatEditing( this.editor, this, /(__)([^_]+)(__)$/g, boldCallback );
  75. }
  76. if ( commands.get( 'italic' ) ) {
  77. const italicCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'italic' );
  78. // The italic autoformatter cannot be triggered by the bold markers, so we need to check the
  79. // text before the pattern (e.g. `(?:^|[^\*])`).
  80. inlineAutoformatEditing( this.editor, this, /(?:^|[^*])(\*)([^*_]+)(\*)$/g, italicCallback );
  81. inlineAutoformatEditing( this.editor, this, /(?:^|[^_])(_)([^_]+)(_)$/g, italicCallback );
  82. }
  83. if ( commands.get( 'code' ) ) {
  84. const codeCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'code' );
  85. inlineAutoformatEditing( this.editor, this, /(`)([^`]+)(`)$/g, codeCallback );
  86. }
  87. if ( commands.get( 'strikethrough' ) ) {
  88. const strikethroughCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'strikethrough' );
  89. inlineAutoformatEditing( this.editor, this, /(~~)([^~]+)(~~)$/g, strikethroughCallback );
  90. }
  91. }
  92. /**
  93. * Adds autoformatting related to {@link module:heading/heading~Heading}.
  94. *
  95. * It is using a number at the end of the command name to associate it with the proper trigger:
  96. *
  97. * * `heading` with value `heading1` will be executed when typing `#`,
  98. * * `heading` with value `heading2` will be executed when typing `##`,
  99. * * ... up to `heading6` and `######`.
  100. *
  101. * @private
  102. */
  103. _addHeadingAutoformats() {
  104. const command = this.editor.commands.get( 'heading' );
  105. if ( command ) {
  106. command.modelElements
  107. .filter( name => name.match( /^heading[1-6]$/ ) )
  108. .forEach( commandValue => {
  109. const level = commandValue[ 7 ];
  110. const pattern = new RegExp( `^(#{${ level }})\\s$` );
  111. blockAutoformatEditing( this.editor, this, pattern, () => {
  112. if ( !command.isEnabled ) {
  113. return false;
  114. }
  115. this.editor.execute( 'heading', { value: commandValue } );
  116. } );
  117. } );
  118. }
  119. }
  120. /**
  121. * Adds autoformatting related to {@link module:block-quote/blockquote~BlockQuote}.
  122. *
  123. * When typed:
  124. * * `> ` – A paragraph will be changed to a block quote.
  125. *
  126. * @private
  127. */
  128. _addBlockQuoteAutoformats() {
  129. if ( this.editor.commands.get( 'blockQuote' ) ) {
  130. blockAutoformatEditing( this.editor, this, /^>\s$/, 'blockQuote' );
  131. }
  132. }
  133. /**
  134. * Adds autoformatting related to {@link module:code-block/codeblock~CodeBlock}.
  135. *
  136. * When typed:
  137. * - `` ``` `` – A paragraph will be changed to a code block.
  138. *
  139. * @private
  140. */
  141. _addCodeBlockAutoformats() {
  142. if ( this.editor.commands.get( 'codeBlock' ) ) {
  143. blockAutoformatEditing( this.editor, this, /^```$/, 'codeBlock' );
  144. }
  145. }
  146. }
  147. // Helper function for getting `inlineAutoformatEditing` callbacks that checks if command is enabled.
  148. //
  149. // @param {module:core/editor/editor~Editor} editor
  150. // @param {String} attributeKey
  151. // @returns {Function}
  152. function getCallbackFunctionForInlineAutoformat( editor, attributeKey ) {
  153. return ( writer, rangesToFormat ) => {
  154. const command = editor.commands.get( attributeKey );
  155. if ( !command.isEnabled ) {
  156. return false;
  157. }
  158. const validRanges = editor.model.schema.getValidRanges( rangesToFormat, attributeKey );
  159. for ( const range of validRanges ) {
  160. writer.setAttribute( attributeKey, true, range );
  161. }
  162. // After applying attribute to the text, remove given attribute from the selection.
  163. // This way user is able to type a text without attribute used by auto formatter.
  164. writer.removeSelectionAttribute( attributeKey );
  165. };
  166. }