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