@ckeditor/ckeditor5-code-block 35.3.2 → 36.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +1 -1
- package/build/code-block.js +2 -2
- package/build/translations/ug.js +1 -1
- package/lang/translations/ug.po +1 -1
- package/package.json +26 -22
- package/src/codeblock.js +13 -151
- package/src/codeblockcommand.js +126 -191
- package/src/codeblockediting.js +358 -445
- package/src/codeblockui.js +69 -96
- package/src/converters.js +206 -240
- package/src/indentcodeblockcommand.js +66 -93
- package/src/index.js +1 -3
- package/src/outdentcodeblockcommand.js +121 -165
- package/src/utils.js +132 -161
- package/theme/codeblock.css +1 -1
- package/build/code-block.js.map +0 -1
package/src/converters.js
CHANGED
|
@@ -1,311 +1,277 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @module code-block/converters
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
5
|
import { getPropertyAssociation } from './utils';
|
|
11
|
-
|
|
12
6
|
/**
|
|
13
7
|
* A model-to-view (both editing and data) converter for the `codeBlock` element.
|
|
14
8
|
*
|
|
15
9
|
* Sample input:
|
|
16
10
|
*
|
|
17
|
-
*
|
|
11
|
+
* ```html
|
|
12
|
+
* <codeBlock language="javascript">foo();<softBreak></softBreak>bar();</codeBlock>
|
|
13
|
+
* ```
|
|
18
14
|
*
|
|
19
15
|
* Sample output (editing):
|
|
20
16
|
*
|
|
21
|
-
*
|
|
17
|
+
* ```html
|
|
18
|
+
* <pre data-language="JavaScript"><code class="language-javascript">foo();<br />bar();</code></pre>
|
|
19
|
+
* ```
|
|
22
20
|
*
|
|
23
21
|
* Sample output (data, see {@link module:code-block/converters~modelToDataViewSoftBreakInsertion}):
|
|
24
22
|
*
|
|
25
|
-
*
|
|
23
|
+
* ```html
|
|
24
|
+
* <pre><code class="language-javascript">foo();\nbar();</code></pre>
|
|
25
|
+
* ```
|
|
26
26
|
*
|
|
27
|
-
* @param
|
|
28
|
-
* @param
|
|
29
|
-
* configuration passed to the feature.
|
|
30
|
-
* @param {Boolean} [useLabels=false] When `true`, the `<pre>` element will get a `data-language` attribute with a
|
|
27
|
+
* @param languageDefs The normalized language configuration passed to the feature.
|
|
28
|
+
* @param useLabels When `true`, the `<pre>` element will get a `data-language` attribute with a
|
|
31
29
|
* human–readable label of the language. Used only in the editing.
|
|
32
|
-
* @returns
|
|
30
|
+
* @returns Returns a conversion callback.
|
|
33
31
|
*/
|
|
34
|
-
export function modelToViewCodeBlockInsertion(
|
|
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
|
-
const pre = writer.createContainerElement( 'pre', preAttributes, code );
|
|
77
|
-
|
|
78
|
-
writer.insert( targetViewPosition, pre );
|
|
79
|
-
mapper.bindElements( data.item, code );
|
|
80
|
-
};
|
|
32
|
+
export function modelToViewCodeBlockInsertion(model, languageDefs, useLabels = false) {
|
|
33
|
+
// Language CSS classes:
|
|
34
|
+
//
|
|
35
|
+
// {
|
|
36
|
+
// php: 'language-php',
|
|
37
|
+
// python: 'language-python',
|
|
38
|
+
// javascript: 'js',
|
|
39
|
+
// ...
|
|
40
|
+
// }
|
|
41
|
+
const languagesToClasses = getPropertyAssociation(languageDefs, 'language', 'class');
|
|
42
|
+
// Language labels:
|
|
43
|
+
//
|
|
44
|
+
// {
|
|
45
|
+
// php: 'PHP',
|
|
46
|
+
// python: 'Python',
|
|
47
|
+
// javascript: 'JavaScript',
|
|
48
|
+
// ...
|
|
49
|
+
// }
|
|
50
|
+
const languagesToLabels = getPropertyAssociation(languageDefs, 'language', 'label');
|
|
51
|
+
return (evt, data, conversionApi) => {
|
|
52
|
+
const { writer, mapper, consumable } = conversionApi;
|
|
53
|
+
if (!consumable.consume(data.item, 'insert')) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const codeBlockLanguage = data.item.getAttribute('language');
|
|
57
|
+
const targetViewPosition = mapper.toViewPosition(model.createPositionBefore(data.item));
|
|
58
|
+
const preAttributes = {};
|
|
59
|
+
// Attributes added only in the editing view.
|
|
60
|
+
if (useLabels) {
|
|
61
|
+
preAttributes['data-language'] = languagesToLabels[codeBlockLanguage];
|
|
62
|
+
preAttributes.spellcheck = 'false';
|
|
63
|
+
}
|
|
64
|
+
const codeAttributes = languagesToClasses[codeBlockLanguage] ? {
|
|
65
|
+
class: languagesToClasses[codeBlockLanguage]
|
|
66
|
+
} : undefined;
|
|
67
|
+
const code = writer.createContainerElement('code', codeAttributes);
|
|
68
|
+
const pre = writer.createContainerElement('pre', preAttributes, code);
|
|
69
|
+
writer.insert(targetViewPosition, pre);
|
|
70
|
+
mapper.bindElements(data.item, code);
|
|
71
|
+
};
|
|
81
72
|
}
|
|
82
|
-
|
|
83
73
|
/**
|
|
84
74
|
* A model-to-data view converter for the new line (`softBreak`) separator.
|
|
85
75
|
*
|
|
86
76
|
* Sample input:
|
|
87
77
|
*
|
|
88
|
-
*
|
|
78
|
+
* ```html
|
|
79
|
+
* <codeBlock ...>foo();<softBreak></softBreak>bar();</codeBlock>
|
|
80
|
+
* ```
|
|
89
81
|
*
|
|
90
82
|
* Sample output:
|
|
91
83
|
*
|
|
92
|
-
*
|
|
84
|
+
* ```html
|
|
85
|
+
* <pre><code ...>foo();\nbar();</code></pre>
|
|
86
|
+
* ```
|
|
93
87
|
*
|
|
94
|
-
* @
|
|
95
|
-
* @returns {Function} Returns a conversion callback.
|
|
88
|
+
* @returns Returns a conversion callback.
|
|
96
89
|
*/
|
|
97
|
-
export function modelToDataViewSoftBreakInsertion(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const position = mapper.toViewPosition( model.createPositionBefore( data.item ) );
|
|
110
|
-
|
|
111
|
-
writer.insert( position, writer.createText( '\n' ) );
|
|
112
|
-
};
|
|
90
|
+
export function modelToDataViewSoftBreakInsertion(model) {
|
|
91
|
+
return (evt, data, conversionApi) => {
|
|
92
|
+
if (data.item.parent.name !== 'codeBlock') {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const { writer, mapper, consumable } = conversionApi;
|
|
96
|
+
if (!consumable.consume(data.item, 'insert')) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const position = mapper.toViewPosition(model.createPositionBefore(data.item));
|
|
100
|
+
writer.insert(position, writer.createText('\n'));
|
|
101
|
+
};
|
|
113
102
|
}
|
|
114
|
-
|
|
115
103
|
/**
|
|
116
104
|
* A view-to-model converter for `<pre>` with the `<code>` HTML.
|
|
117
105
|
*
|
|
118
106
|
* Sample input:
|
|
119
107
|
*
|
|
120
|
-
*
|
|
108
|
+
* ```html
|
|
109
|
+
* <pre><code class="language-javascript">foo();bar();</code></pre>
|
|
110
|
+
* ```
|
|
121
111
|
*
|
|
122
112
|
* Sample output:
|
|
123
113
|
*
|
|
124
|
-
*
|
|
114
|
+
* ```html
|
|
115
|
+
* <codeBlock language="javascript">foo();bar();</codeBlock>
|
|
116
|
+
* ```
|
|
125
117
|
*
|
|
126
|
-
* @param
|
|
127
|
-
* @
|
|
128
|
-
* configuration passed to the feature.
|
|
129
|
-
* @returns {Function} Returns a conversion callback.
|
|
118
|
+
* @param languageDefs The normalized language configuration passed to the feature.
|
|
119
|
+
* @returns Returns a conversion callback.
|
|
130
120
|
*/
|
|
131
|
-
export function dataViewToModelCodeBlockInsertion(
|
|
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
|
-
writer.setAttribute( 'language', defaultLanguageName, codeBlock );
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
conversionApi.convertChildren( viewCodeElement, codeBlock );
|
|
189
|
-
|
|
190
|
-
// Let's try to insert code block.
|
|
191
|
-
if ( !conversionApi.safeInsert( codeBlock, data.modelCursor ) ) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
consumable.consume( viewCodeElement, { name: true } );
|
|
196
|
-
|
|
197
|
-
conversionApi.updateConversionResult( codeBlock, data );
|
|
198
|
-
};
|
|
121
|
+
export function dataViewToModelCodeBlockInsertion(editingView, languageDefs) {
|
|
122
|
+
// Language names associated with CSS classes:
|
|
123
|
+
//
|
|
124
|
+
// {
|
|
125
|
+
// 'language-php': 'php',
|
|
126
|
+
// 'language-python': 'python',
|
|
127
|
+
// js: 'javascript',
|
|
128
|
+
// ...
|
|
129
|
+
// }
|
|
130
|
+
const classesToLanguages = getPropertyAssociation(languageDefs, 'class', 'language');
|
|
131
|
+
const defaultLanguageName = languageDefs[0].language;
|
|
132
|
+
return (evt, data, conversionApi) => {
|
|
133
|
+
const viewCodeElement = data.viewItem;
|
|
134
|
+
const viewPreElement = viewCodeElement.parent;
|
|
135
|
+
if (!viewPreElement || !viewPreElement.is('element', 'pre')) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// In case of nested code blocks we don't want to convert to another code block.
|
|
139
|
+
if (data.modelCursor.findAncestor('codeBlock')) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const { consumable, writer } = conversionApi;
|
|
143
|
+
if (!consumable.test(viewCodeElement, { name: true })) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const codeBlock = writer.createElement('codeBlock');
|
|
147
|
+
const viewChildClasses = [...viewCodeElement.getClassNames()];
|
|
148
|
+
// As we're to associate each class with a model language, a lack of class (empty class) can be
|
|
149
|
+
// also associated with a language if the language definition was configured so. Pushing an empty
|
|
150
|
+
// string to make sure the association will work.
|
|
151
|
+
if (!viewChildClasses.length) {
|
|
152
|
+
viewChildClasses.push('');
|
|
153
|
+
}
|
|
154
|
+
// Figure out if any of the <code> element's class names is a valid programming
|
|
155
|
+
// language class. If so, use it on the model element (becomes the language of the entire block).
|
|
156
|
+
for (const className of viewChildClasses) {
|
|
157
|
+
const language = classesToLanguages[className];
|
|
158
|
+
if (language) {
|
|
159
|
+
writer.setAttribute('language', language, codeBlock);
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// If no language value was set, use the default language from the config.
|
|
164
|
+
if (!codeBlock.hasAttribute('language')) {
|
|
165
|
+
writer.setAttribute('language', defaultLanguageName, codeBlock);
|
|
166
|
+
}
|
|
167
|
+
conversionApi.convertChildren(viewCodeElement, codeBlock);
|
|
168
|
+
// Let's try to insert code block.
|
|
169
|
+
if (!conversionApi.safeInsert(codeBlock, data.modelCursor)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
consumable.consume(viewCodeElement, { name: true });
|
|
173
|
+
conversionApi.updateConversionResult(codeBlock, data);
|
|
174
|
+
};
|
|
199
175
|
}
|
|
200
|
-
|
|
201
176
|
/**
|
|
202
177
|
* A view-to-model converter for new line characters in `<pre>`.
|
|
203
178
|
*
|
|
204
179
|
* Sample input:
|
|
205
180
|
*
|
|
206
|
-
*
|
|
181
|
+
* ```html
|
|
182
|
+
* <pre><code class="language-javascript">foo();\nbar();</code></pre>
|
|
183
|
+
* ```
|
|
207
184
|
*
|
|
208
185
|
* Sample output:
|
|
209
186
|
*
|
|
210
|
-
*
|
|
187
|
+
* ```html
|
|
188
|
+
* <codeBlock language="javascript">foo();<softBreak></softBreak>bar();</codeBlock>
|
|
189
|
+
* ```
|
|
211
190
|
*
|
|
212
191
|
* @returns {Function} Returns a conversion callback.
|
|
213
192
|
*/
|
|
214
193
|
export function dataViewToModelTextNewlinesInsertion() {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
writer.insert( softBreak, position );
|
|
242
|
-
position = writer.createPositionAfter( softBreak );
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
data.modelRange = writer.createRange(
|
|
247
|
-
data.modelCursor,
|
|
248
|
-
position
|
|
249
|
-
);
|
|
250
|
-
data.modelCursor = position;
|
|
251
|
-
};
|
|
194
|
+
return (evt, data, { consumable, writer }) => {
|
|
195
|
+
let position = data.modelCursor;
|
|
196
|
+
// When node is already converted then do nothing.
|
|
197
|
+
if (!consumable.test(data.viewItem)) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
// When not inside `codeBlock` then do nothing.
|
|
201
|
+
if (!position.findAncestor('codeBlock')) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
consumable.consume(data.viewItem);
|
|
205
|
+
const text = data.viewItem.data;
|
|
206
|
+
const textLines = text.split('\n').map(data => writer.createText(data));
|
|
207
|
+
const lastLine = textLines[textLines.length - 1];
|
|
208
|
+
for (const node of textLines) {
|
|
209
|
+
writer.insert(node, position);
|
|
210
|
+
position = position.getShiftedBy(node.offsetSize);
|
|
211
|
+
if (node !== lastLine) {
|
|
212
|
+
const softBreak = writer.createElement('softBreak');
|
|
213
|
+
writer.insert(softBreak, position);
|
|
214
|
+
position = writer.createPositionAfter(softBreak);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
data.modelRange = writer.createRange(data.modelCursor, position);
|
|
218
|
+
data.modelCursor = position;
|
|
219
|
+
};
|
|
252
220
|
}
|
|
253
|
-
|
|
254
221
|
/**
|
|
255
222
|
* A view-to-model converter that handles orphan text nodes (white spaces, new lines, etc.)
|
|
256
223
|
* that surround `<code>` inside `<pre>`.
|
|
257
224
|
*
|
|
258
225
|
* Sample input:
|
|
259
226
|
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
227
|
+
* ```html
|
|
228
|
+
* // White spaces
|
|
229
|
+
* <pre> <code>foo()</code> </pre>
|
|
262
230
|
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
231
|
+
* // White spaces
|
|
232
|
+
* <pre> <code>foo()</code> </pre>
|
|
265
233
|
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
234
|
+
* // White spaces
|
|
235
|
+
* <pre> <code>foo()</code> </pre>
|
|
268
236
|
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
237
|
+
* // New lines
|
|
238
|
+
* <pre>
|
|
239
|
+
* <code>foo()</code>
|
|
240
|
+
* </pre>
|
|
273
241
|
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
242
|
+
* // Redundant text
|
|
243
|
+
* <pre>ABC<code>foo()</code>DEF</pre>
|
|
244
|
+
* ```
|
|
276
245
|
*
|
|
277
246
|
* Unified output for each case:
|
|
278
247
|
*
|
|
279
|
-
*
|
|
248
|
+
* ```html
|
|
249
|
+
* <codeBlock language="plaintext">foo()</codeBlock>
|
|
250
|
+
* ```
|
|
280
251
|
*
|
|
281
|
-
* @returns
|
|
252
|
+
* @returns Returns a conversion callback.
|
|
282
253
|
*/
|
|
283
254
|
export function dataViewToModelOrphanNodeConsumer() {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
// Consuming the orphan to remove it from the input data.
|
|
307
|
-
// Second argument in `consumable.consume` is discarded for text nodes.
|
|
308
|
-
consumable.consume( child, { name: true } );
|
|
309
|
-
}
|
|
310
|
-
};
|
|
255
|
+
return (evt, data, { consumable }) => {
|
|
256
|
+
const preElement = data.viewItem;
|
|
257
|
+
// Don't clean up nested pre elements. Their content should stay as it is, they are not upcasted
|
|
258
|
+
// to code blocks.
|
|
259
|
+
if (preElement.findAncestor('pre')) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const preChildren = Array.from(preElement.getChildren());
|
|
263
|
+
const childCodeElement = preChildren.find(node => node.is('element', 'code'));
|
|
264
|
+
// <code>-less <pre>. It will not upcast to code block in the model, skipping.
|
|
265
|
+
if (!childCodeElement) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
for (const child of preChildren) {
|
|
269
|
+
if (child === childCodeElement || !child.is('$text')) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
// Consuming the orphan to remove it from the input data.
|
|
273
|
+
// Second argument in `consumable.consume` is discarded for text nodes.
|
|
274
|
+
consumable.consume(child, { name: true });
|
|
275
|
+
}
|
|
276
|
+
};
|
|
311
277
|
}
|