UNPKG

7.66 kBJavaScriptView Raw
1/**
2 * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4 */
5
6/**
7 * @module autoformat/autoformat
8 */
9
10import { Plugin } from 'ckeditor5/src/core';
11import { Delete } from 'ckeditor5/src/typing';
12
13import blockAutoformatEditing from './blockautoformatediting';
14import inlineAutoformatEditing from './inlineautoformatediting';
15
16/**
17 * Enables a set of predefined autoformatting actions.
18 *
19 * For a detailed overview, check the {@glink features/autoformat Autoformatting feature documentation}
20 * and the {@glink api/autoformat package page}.
21 *
22 * @extends module:core/plugin~Plugin
23 */
24export default class Autoformat extends Plugin {
25 /**
26 * @inheritdoc
27 */
28 static get requires() {
29 return [ Delete ];
30 }
31
32 /**
33 * @inheritDoc
34 */
35 static get pluginName() {
36 return 'Autoformat';
37 }
38
39 /**
40 * @inheritDoc
41 */
42 afterInit() {
43 this._addListAutoformats();
44 this._addBasicStylesAutoformats();
45 this._addHeadingAutoformats();
46 this._addBlockQuoteAutoformats();
47 this._addCodeBlockAutoformats();
48 this._addHorizontalLineAutoformats();
49 }
50
51 /**
52 * Adds autoformatting related to the {@link module:list/list~List}.
53 *
54 * When typed:
55 * - `* ` or `- ` – A paragraph will be changed to a bulleted list.
56 * - `1. ` or `1) ` – A paragraph will be changed to a numbered list ("1" can be any digit or a list of digits).
57 * - `[] ` or `[ ] ` – A paragraph will be changed to a to-do list.
58 * - `[x] ` or `[ x ] ` – A paragraph will be changed to a checked to-do list.
59 *
60 * @private
61 */
62 _addListAutoformats() {
63 const commands = this.editor.commands;
64
65 if ( commands.get( 'bulletedList' ) ) {
66 blockAutoformatEditing( this.editor, this, /^[*-]\s$/, 'bulletedList' );
67 }
68
69 if ( commands.get( 'numberedList' ) ) {
70 blockAutoformatEditing( this.editor, this, /^1[.|)]\s$/, 'numberedList' );
71 }
72
73 if ( commands.get( 'todoList' ) ) {
74 blockAutoformatEditing( this.editor, this, /^\[\s?\]\s$/, 'todoList' );
75 }
76
77 if ( commands.get( 'checkTodoList' ) ) {
78 blockAutoformatEditing( this.editor, this, /^\[\s?x\s?\]\s$/, () => {
79 this.editor.execute( 'todoList' );
80 this.editor.execute( 'checkTodoList' );
81 } );
82 }
83 }
84
85 /**
86 * Adds autoformatting related to the {@link module:basic-styles/bold~Bold},
87 * {@link module:basic-styles/italic~Italic}, {@link module:basic-styles/code~Code}
88 * and {@link module:basic-styles/strikethrough~Strikethrough}
89 *
90 * When typed:
91 * - `**foobar**` – `**` characters are removed and `foobar` is set to bold,
92 * - `__foobar__` – `__` characters are removed and `foobar` is set to bold,
93 * - `*foobar*` – `*` characters are removed and `foobar` is set to italic,
94 * - `_foobar_` – `_` characters are removed and `foobar` is set to italic,
95 * - ``` `foobar` – ``` ` ``` characters are removed and `foobar` is set to code,
96 * - `~~foobar~~` – `~~` characters are removed and `foobar` is set to strikethrough.
97 *
98 * @private
99 */
100 _addBasicStylesAutoformats() {
101 const commands = this.editor.commands;
102
103 if ( commands.get( 'bold' ) ) {
104 const boldCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'bold' );
105
106 inlineAutoformatEditing( this.editor, this, /(?:^|\s)(\*\*)([^*]+)(\*\*)$/g, boldCallback );
107 inlineAutoformatEditing( this.editor, this, /(?:^|\s)(__)([^_]+)(__)$/g, boldCallback );
108 }
109
110 if ( commands.get( 'italic' ) ) {
111 const italicCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'italic' );
112
113 // The italic autoformatter cannot be triggered by the bold markers, so we need to check the
114 // text before the pattern (e.g. `(?:^|[^\*])`).
115 inlineAutoformatEditing( this.editor, this, /(?:^|\s)(\*)([^*_]+)(\*)$/g, italicCallback );
116 inlineAutoformatEditing( this.editor, this, /(?:^|\s)(_)([^_]+)(_)$/g, italicCallback );
117 }
118
119 if ( commands.get( 'code' ) ) {
120 const codeCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'code' );
121
122 inlineAutoformatEditing( this.editor, this, /(`)([^`]+)(`)$/g, codeCallback );
123 }
124
125 if ( commands.get( 'strikethrough' ) ) {
126 const strikethroughCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'strikethrough' );
127
128 inlineAutoformatEditing( this.editor, this, /(~~)([^~]+)(~~)$/g, strikethroughCallback );
129 }
130 }
131
132 /**
133 * Adds autoformatting related to {@link module:heading/heading~Heading}.
134 *
135 * It is using a number at the end of the command name to associate it with the proper trigger:
136 *
137 * * `heading` with value `heading1` will be executed when typing `#`,
138 * * `heading` with value `heading2` will be executed when typing `##`,
139 * * ... up to `heading6` and `######`.
140 *
141 * @private
142 */
143 _addHeadingAutoformats() {
144 const command = this.editor.commands.get( 'heading' );
145
146 if ( command ) {
147 command.modelElements
148 .filter( name => name.match( /^heading[1-6]$/ ) )
149 .forEach( modelName => {
150 const level = modelName[ 7 ];
151 const pattern = new RegExp( `^(#{${ level }})\\s$` );
152
153 blockAutoformatEditing( this.editor, this, pattern, () => {
154 // Should only be active if command is enabled and heading style associated with pattern is inactive.
155 if ( !command.isEnabled || command.value === modelName ) {
156 return false;
157 }
158
159 this.editor.execute( 'heading', { value: modelName } );
160 } );
161 } );
162 }
163 }
164
165 /**
166 * Adds autoformatting related to {@link module:block-quote/blockquote~BlockQuote}.
167 *
168 * When typed:
169 * * `> ` – A paragraph will be changed to a block quote.
170 *
171 * @private
172 */
173 _addBlockQuoteAutoformats() {
174 if ( this.editor.commands.get( 'blockQuote' ) ) {
175 blockAutoformatEditing( this.editor, this, /^>\s$/, 'blockQuote' );
176 }
177 }
178
179 /**
180 * Adds autoformatting related to {@link module:code-block/codeblock~CodeBlock}.
181 *
182 * When typed:
183 * - `` ``` `` – A paragraph will be changed to a code block.
184 *
185 * @private
186 */
187 _addCodeBlockAutoformats() {
188 const editor = this.editor;
189 const selection = editor.model.document.selection;
190
191 if ( editor.commands.get( 'codeBlock' ) ) {
192 blockAutoformatEditing( editor, this, /^```$/, () => {
193 if ( selection.getFirstPosition().parent.is( 'element', 'listItem' ) ) {
194 return false;
195 }
196 this.editor.execute( 'codeBlock', {
197 usePreviousLanguageChoice: true
198 } );
199 } );
200 }
201 }
202
203 /**
204 * Adds autoformatting related to {@link module:horizontal-line/horizontalline~HorizontalLine}.
205 *
206 * When typed:
207 * - `` --- `` – Will be replaced with a horizontal line.
208 *
209 * @private
210 */
211 _addHorizontalLineAutoformats() {
212 if ( this.editor.commands.get( 'horizontalLine' ) ) {
213 blockAutoformatEditing( this.editor, this, /^---$/, 'horizontalLine' );
214 }
215 }
216}
217
218// Helper function for getting `inlineAutoformatEditing` callbacks that checks if command is enabled.
219//
220// @param {module:core/editor/editor~Editor} editor
221// @param {String} attributeKey
222// @returns {Function}
223function getCallbackFunctionForInlineAutoformat( editor, attributeKey ) {
224 return ( writer, rangesToFormat ) => {
225 const command = editor.commands.get( attributeKey );
226
227 if ( !command.isEnabled ) {
228 return false;
229 }
230
231 const validRanges = editor.model.schema.getValidRanges( rangesToFormat, attributeKey );
232
233 for ( const range of validRanges ) {
234 writer.setAttribute( attributeKey, true, range );
235 }
236
237 // After applying attribute to the text, remove given attribute from the selection.
238 // This way user is able to type a text without attribute used by auto formatter.
239 writer.removeSelectionAttribute( attributeKey );
240 };
241}
242
\No newline at end of file