• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ckeditor / ckeditor5 / b3dabf98-2b9b-44b5-be08-f689e3b11415

16 Dec 2024 09:55AM UTC coverage: 100.0%. Remained the same
b3dabf98-2b9b-44b5-be08-f689e3b11415

push

circleci

web-flow
Merge stable into master

14416 of 14416 branches covered (100.0%)

Branch coverage included in aggregate %.

38314 of 38314 relevant lines covered (100.0%)

10098.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/packages/ckeditor5-autoformat/src/autoformat.ts
1
/**
2
 * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
 */
5

6
/**
7
 * @module autoformat/autoformat
8
 */
9
import type { HeadingCommand } from '@ckeditor/ckeditor5-heading';
10

11
import { Plugin, type Editor } from 'ckeditor5/src/core.js';
12
import type { Range, Writer } from 'ckeditor5/src/engine.js';
13
import { Delete } from 'ckeditor5/src/typing.js';
14

15
import blockAutoformatEditing from './blockautoformatediting.js';
16
import inlineAutoformatEditing from './inlineautoformatediting.js';
17

18
/**
19
 * Enables a set of predefined autoformatting actions.
20
 *
21
 * For a detailed overview, check the {@glink features/autoformat Autoformatting} feature guide
22
 * and the {@glink api/autoformat package page}.
23
 */
24
export default class Autoformat extends Plugin {
25
        /**
26
         * @inheritDoc
27
         */
28
        public static get requires() {
29
                return [ Delete ] as const;
1,440✔
30
        }
31

32
        /**
33
         * @inheritDoc
34
         */
35
        public static get pluginName() {
36
                return 'Autoformat' as const;
1,153✔
37
        }
38

39
        /**
40
         * @inheritDoc
41
         */
42
        public static override get isOfficialPlugin(): true {
43
                return true;
1✔
44
        }
45

46
        /**
47
         * @inheritDoc
48
         */
49
        public afterInit(): void {
50
                const editor = this.editor;
288✔
51
                const t = this.editor.t;
288✔
52

53
                this._addListAutoformats();
288✔
54
                this._addBasicStylesAutoformats();
288✔
55
                this._addHeadingAutoformats();
288✔
56
                this._addBlockQuoteAutoformats();
288✔
57
                this._addCodeBlockAutoformats();
288✔
58
                this._addHorizontalLineAutoformats();
288✔
59

60
                // Add the information about the keystroke to the accessibility database.
61
                editor.accessibility.addKeystrokeInfos( {
288✔
62
                        keystrokes: [
63
                                {
64
                                        label: t( 'Revert autoformatting action' ),
65
                                        keystroke: 'Backspace'
66
                                }
67
                        ]
68
                } );
69
        }
70

71
        /**
72
         * Adds autoformatting related to the {@link module:list/list~List}.
73
         *
74
         * When typed:
75
         * - `* ` or `- ` – A paragraph will be changed into a bulleted list.
76
         * - `1. ` or `1) ` – A paragraph will be changed into a numbered list ("1" can be any digit or a list of digits).
77
         * - `[] ` or `[ ] ` – A paragraph will be changed into a to-do list.
78
         * - `[x] ` or `[ x ] ` – A paragraph will be changed into a checked to-do list.
79
         */
80
        private _addListAutoformats(): void {
81
                const commands = this.editor.commands;
288✔
82

83
                if ( commands.get( 'bulletedList' ) ) {
288✔
84
                        blockAutoformatEditing( this.editor, this, /^[*-]\s$/, 'bulletedList' );
234✔
85
                }
86

87
                if ( commands.get( 'numberedList' ) ) {
288✔
88
                        blockAutoformatEditing( this.editor, this, /^1[.|)]\s$/, 'numberedList' );
234✔
89
                }
90

91
                if ( commands.get( 'todoList' ) ) {
288✔
92
                        blockAutoformatEditing( this.editor, this, /^\[\s?\]\s$/, 'todoList' );
216✔
93
                }
94

95
                if ( commands.get( 'checkTodoList' ) ) {
288✔
96
                        blockAutoformatEditing( this.editor, this, /^\[\s?x\s?\]\s$/, () => {
216✔
97
                                this.editor.execute( 'todoList' );
16✔
98
                                this.editor.execute( 'checkTodoList' );
16✔
99
                        } );
100
                }
101
        }
102

103
        /**
104
         * Adds autoformatting related to the {@link module:basic-styles/bold~Bold},
105
         * {@link module:basic-styles/italic~Italic}, {@link module:basic-styles/code~Code}
106
         * and {@link module:basic-styles/strikethrough~Strikethrough}
107
         *
108
         * When typed:
109
         * - `**foobar**` – `**` characters are removed and `foobar` is set to bold,
110
         * - `__foobar__` – `__` characters are removed and `foobar` is set to bold,
111
         * - `*foobar*` – `*` characters are removed and `foobar` is set to italic,
112
         * - `_foobar_` – `_` characters are removed and `foobar` is set to italic,
113
         * - ``` `foobar` – ``` ` ``` characters are removed and `foobar` is set to code,
114
         * - `~~foobar~~` – `~~` characters are removed and `foobar` is set to strikethrough.
115
         */
116
        private _addBasicStylesAutoformats(): void {
117
                const commands = this.editor.commands;
288✔
118

119
                if ( commands.get( 'bold' ) ) {
288✔
120
                        const boldCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'bold' );
232✔
121

122
                        inlineAutoformatEditing( this.editor, this, /(?:^|\s)(\*\*)([^*]+)(\*\*)$/g, boldCallback );
232✔
123
                        inlineAutoformatEditing( this.editor, this, /(?:^|\s)(__)([^_]+)(__)$/g, boldCallback );
232✔
124
                }
125

126
                if ( commands.get( 'italic' ) ) {
288✔
127
                        const italicCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'italic' );
229✔
128

129
                        // The italic autoformatter cannot be triggered by the bold markers, so we need to check the
130
                        // text before the pattern (e.g. `(?:^|[^\*])`).
131
                        inlineAutoformatEditing( this.editor, this, /(?:^|\s)(\*)([^*_]+)(\*)$/g, italicCallback );
229✔
132
                        inlineAutoformatEditing( this.editor, this, /(?:^|\s)(_)([^_]+)(_)$/g, italicCallback );
229✔
133
                }
134

135
                if ( commands.get( 'code' ) ) {
288✔
136
                        const codeCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'code' );
229✔
137

138
                        inlineAutoformatEditing( this.editor, this, /(`)([^`]+)(`)$/g, codeCallback );
229✔
139
                }
140

141
                if ( commands.get( 'strikethrough' ) ) {
288✔
142
                        const strikethroughCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'strikethrough' );
229✔
143

144
                        inlineAutoformatEditing( this.editor, this, /(~~)([^~]+)(~~)$/g, strikethroughCallback );
229✔
145
                }
146
        }
147

148
        /**
149
         * Adds autoformatting related to {@link module:heading/heading~Heading}.
150
         *
151
         * It is using a number at the end of the command name to associate it with the proper trigger:
152
         *
153
         * * `heading` with a `heading1` value will be executed when typing `#`,
154
         * * `heading` with a `heading2` value will be executed when typing `##`,
155
         * * ... up to `heading6` for `######`.
156
         */
157
        private _addHeadingAutoformats(): void {
158
                const command: HeadingCommand | undefined = this.editor.commands.get( 'heading' );
288✔
159

160
                if ( command ) {
288✔
161
                        command.modelElements
233✔
162
                                .filter( name => name.match( /^heading[1-6]$/ ) )
693✔
163
                                .forEach( modelName => {
164
                                        const level = modelName[ 7 ];
693✔
165
                                        const pattern = new RegExp( `^(#{${ level }})\\s$` );
693✔
166

167
                                        blockAutoformatEditing( this.editor, this, pattern, () => {
693✔
168
                                                // Should only be active if command is enabled and heading style associated with pattern is inactive.
169
                                                if ( !command.isEnabled || command.value === modelName ) {
16✔
170
                                                        return false;
4✔
171
                                                }
172

173
                                                this.editor.execute( 'heading', { value: modelName } );
12✔
174
                                        } );
175
                                } );
176
                }
177
        }
178

179
        /**
180
         * Adds autoformatting related to {@link module:block-quote/blockquote~BlockQuote}.
181
         *
182
         * When typed:
183
         * * `> ` – A paragraph will be changed to a block quote.
184
         */
185
        private _addBlockQuoteAutoformats(): void {
186
                if ( this.editor.commands.get( 'blockQuote' ) ) {
288✔
187
                        blockAutoformatEditing( this.editor, this, /^>\s$/, 'blockQuote' );
229✔
188
                }
189
        }
190

191
        /**
192
         * Adds autoformatting related to {@link module:code-block/codeblock~CodeBlock}.
193
         *
194
         * When typed:
195
         * - `` ``` `` – A paragraph will be changed to a code block.
196
         */
197
        private _addCodeBlockAutoformats(): void {
198
                const editor = this.editor;
288✔
199
                const selection = editor.model.document.selection;
288✔
200

201
                if ( editor.commands.get( 'codeBlock' ) ) {
288✔
202
                        blockAutoformatEditing( editor, this, /^```$/, () => {
216✔
203
                                if ( selection.getFirstPosition()!.parent.is( 'element', 'listItem' ) ) {
12✔
204
                                        return false;
2✔
205
                                }
206
                                this.editor.execute( 'codeBlock', {
10✔
207
                                        usePreviousLanguageChoice: true
208
                                } );
209
                        } );
210
                }
211
        }
212

213
        /**
214
         * Adds autoformatting related to {@link module:horizontal-line/horizontalline~HorizontalLine}.
215
         *
216
         * When typed:
217
         * - `` --- `` – Will be replaced with a horizontal line.
218
         */
219
        private _addHorizontalLineAutoformats(): void {
220
                if ( this.editor.commands.get( 'horizontalLine' ) ) {
288✔
221
                        blockAutoformatEditing( this.editor, this, /^---$/, 'horizontalLine' );
216✔
222
                }
223
        }
224
}
225

226
/**
227
 * Helper function for getting `inlineAutoformatEditing` callbacks that checks if command is enabled.
228
 */
229
function getCallbackFunctionForInlineAutoformat( editor: Editor, attributeKey: string ) {
230
        return ( writer: Writer, rangesToFormat: Array<Range> ): boolean | undefined => {
919✔
231
                const command = editor.commands.get( attributeKey )!;
30✔
232

233
                if ( !command.isEnabled ) {
30✔
234
                        return false;
2✔
235
                }
236

237
                const validRanges = editor.model.schema.getValidRanges( rangesToFormat, attributeKey );
28✔
238

239
                for ( const range of validRanges ) {
28✔
240
                        writer.setAttribute( attributeKey, true, range );
28✔
241
                }
242

243
                // After applying attribute to the text, remove given attribute from the selection.
244
                // This way user is able to type a text without attribute used by auto formatter.
245
                writer.removeSelectionAttribute( attributeKey );
28✔
246
        };
247
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc