@ckeditor/ckeditor5-code-block 40.0.0 → 40.1.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/src/utils.js CHANGED
@@ -1,209 +1,209 @@
1
- /**
2
- * @license Copyright (c) 2003-2023, 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
- import { first } from 'ckeditor5/src/utils';
6
- /**
7
- * Returns code block languages as defined in `config.codeBlock.languages` but processed:
8
- *
9
- * * To consider the editor localization, i.e. to display {@link module:code-block/codeblockconfig~CodeBlockLanguageDefinition}
10
- * in the correct language. There is no way to use {@link module:utils/locale~Locale#t} when the user
11
- * configuration is defined because the editor does not exist yet.
12
- * * To make sure each definition has a CSS class associated with it even if not specified
13
- * in the original configuration.
14
- */
15
- export function getNormalizedAndLocalizedLanguageDefinitions(editor) {
16
- const t = editor.t;
17
- const languageDefs = editor.config.get('codeBlock.languages');
18
- for (const def of languageDefs) {
19
- if (def.label === 'Plain text') {
20
- def.label = t('Plain text');
21
- }
22
- if (def.class === undefined) {
23
- def.class = `language-${def.language}`;
24
- }
25
- }
26
- return languageDefs;
27
- }
28
- /**
29
- * Returns an object associating certain language definition properties with others. For instance:
30
- *
31
- * For:
32
- *
33
- * ```ts
34
- * const definitions = {
35
- * { language: 'php', class: 'language-php', label: 'PHP' },
36
- * { language: 'javascript', class: 'js', label: 'JavaScript' },
37
- * };
38
- *
39
- * getPropertyAssociation( definitions, 'class', 'language' );
40
- * ```
41
- *
42
- * returns:
43
- *
44
- * ```ts
45
- * {
46
- * 'language-php': 'php',
47
- * 'js': 'javascript'
48
- * }
49
- * ```
50
- *
51
- * and
52
- *
53
- * ```ts
54
- * getPropertyAssociation( definitions, 'language', 'label' );
55
- * ```
56
- *
57
- * returns:
58
- *
59
- * ```ts
60
- * {
61
- * 'php': 'PHP',
62
- * 'javascript': 'JavaScript'
63
- * }
64
- * ```
65
- */
66
- export function getPropertyAssociation(languageDefs, key, value) {
67
- const association = {};
68
- for (const def of languageDefs) {
69
- if (key === 'class') {
70
- // Only the first class is considered.
71
- const newKey = (def[key]).split(' ').shift();
72
- association[newKey] = def[value];
73
- }
74
- else {
75
- association[def[key]] = def[value];
76
- }
77
- }
78
- return association;
79
- }
80
- /**
81
- * For a given model text node, it returns white spaces that precede other characters in that node.
82
- * This corresponds to the indentation part of the code block line.
83
- */
84
- export function getLeadingWhiteSpaces(textNode) {
85
- return textNode.data.match(/^(\s*)/)[0];
86
- }
87
- /**
88
- * For plain text containing the code (a snippet), it returns a document fragment containing
89
- * view text nodes separated by `<br>` elements (in place of new line characters "\n"), for instance:
90
- *
91
- * Input:
92
- *
93
- * ```ts
94
- * "foo()\n
95
- * bar()"
96
- * ```
97
- *
98
- * Output:
99
- *
100
- * ```html
101
- * <DocumentFragment>
102
- * "foo()"
103
- * <br/>
104
- * "bar()"
105
- * </DocumentFragment>
106
- * ```
107
- *
108
- * @param text The raw code text to be converted.
109
- */
110
- export function rawSnippetTextToViewDocumentFragment(writer, text) {
111
- const fragment = writer.createDocumentFragment();
112
- const textLines = text.split('\n');
113
- const items = textLines.reduce((nodes, line, lineIndex) => {
114
- nodes.push(line);
115
- if (lineIndex < textLines.length - 1) {
116
- nodes.push(writer.createElement('br'));
117
- }
118
- return nodes;
119
- }, []);
120
- writer.appendChild(items, fragment);
121
- return fragment;
122
- }
123
- /**
124
- * Returns an array of all model positions within the selection that represent code block lines.
125
- *
126
- * If the selection is collapsed, it returns the exact selection anchor position:
127
- *
128
- * ```html
129
- * <codeBlock>[]foo</codeBlock> -> <codeBlock>^foo</codeBlock>
130
- * <codeBlock>foo[]bar</codeBlock> -> <codeBlock>foo^bar</codeBlock>
131
- * ```
132
- *
133
- * Otherwise, it returns positions **before** each text node belonging to all code blocks contained by the selection:
134
- *
135
- * ```html
136
- * <codeBlock> <codeBlock>
137
- * foo[bar ^foobar
138
- * <softBreak></softBreak> -> <softBreak></softBreak>
139
- * baz]qux ^bazqux
140
- * </codeBlock> </codeBlock>
141
- * ```
142
- *
143
- * It also works across other non–code blocks:
144
- *
145
- * ```html
146
- * <codeBlock> <codeBlock>
147
- * foo[bar ^foobar
148
- * </codeBlock> </codeBlock>
149
- * <paragraph>text</paragraph> -> <paragraph>text</paragraph>
150
- * <codeBlock> <codeBlock>
151
- * baz]qux ^bazqux
152
- * </codeBlock> </codeBlock>
153
- * ```
154
- *
155
- * **Note:** The positions are in reverse order so they do not get outdated when iterating over them and
156
- * the writer inserts or removes elements at the same time.
157
- *
158
- * **Note:** The position is located after the leading white spaces in the text node.
159
- */
160
- export function getIndentOutdentPositions(model) {
161
- const selection = model.document.selection;
162
- const positions = [];
163
- // When the selection is collapsed, there's only one position we can indent or outdent.
164
- if (selection.isCollapsed) {
165
- return [selection.anchor];
166
- }
167
- // When the selection is NOT collapsed, collect all positions starting before text nodes
168
- // (code lines) in any <codeBlock> within the selection.
169
- // Walk backward so positions we are about to collect here do not get outdated when
170
- // inserting or deleting using the writer.
171
- const walker = selection.getFirstRange().getWalker({
172
- ignoreElementEnd: true,
173
- direction: 'backward'
174
- });
175
- for (const { item } of walker) {
176
- if (!item.is('$textProxy')) {
177
- continue;
178
- }
179
- const { parent, startOffset } = item.textNode;
180
- if (!parent.is('element', 'codeBlock')) {
181
- continue;
182
- }
183
- const leadingWhiteSpaces = getLeadingWhiteSpaces(item.textNode);
184
- // Make sure the position is after all leading whitespaces in the text node.
185
- const position = model.createPositionAt(parent, startOffset + leadingWhiteSpaces.length);
186
- positions.push(position);
187
- }
188
- return positions;
189
- }
190
- /**
191
- * Checks if any of the blocks within the model selection is a code block.
192
- */
193
- export function isModelSelectionInCodeBlock(selection) {
194
- const firstBlock = first(selection.getSelectedBlocks());
195
- return !!firstBlock && firstBlock.is('element', 'codeBlock');
196
- }
197
- /**
198
- * Checks if an {@link module:engine/model/element~Element Element} can become a code block.
199
- *
200
- * @param schema Model's schema.
201
- * @param element The element to be checked.
202
- * @returns Check result.
203
- */
204
- export function canBeCodeBlock(schema, element) {
205
- if (element.is('rootElement') || schema.isLimit(element)) {
206
- return false;
207
- }
208
- return schema.checkChild(element.parent, 'codeBlock');
209
- }
1
+ /**
2
+ * @license Copyright (c) 2003-2023, 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
+ import { first } from 'ckeditor5/src/utils';
6
+ /**
7
+ * Returns code block languages as defined in `config.codeBlock.languages` but processed:
8
+ *
9
+ * * To consider the editor localization, i.e. to display {@link module:code-block/codeblockconfig~CodeBlockLanguageDefinition}
10
+ * in the correct language. There is no way to use {@link module:utils/locale~Locale#t} when the user
11
+ * configuration is defined because the editor does not exist yet.
12
+ * * To make sure each definition has a CSS class associated with it even if not specified
13
+ * in the original configuration.
14
+ */
15
+ export function getNormalizedAndLocalizedLanguageDefinitions(editor) {
16
+ const t = editor.t;
17
+ const languageDefs = editor.config.get('codeBlock.languages');
18
+ for (const def of languageDefs) {
19
+ if (def.label === 'Plain text') {
20
+ def.label = t('Plain text');
21
+ }
22
+ if (def.class === undefined) {
23
+ def.class = `language-${def.language}`;
24
+ }
25
+ }
26
+ return languageDefs;
27
+ }
28
+ /**
29
+ * Returns an object associating certain language definition properties with others. For instance:
30
+ *
31
+ * For:
32
+ *
33
+ * ```ts
34
+ * const definitions = {
35
+ * { language: 'php', class: 'language-php', label: 'PHP' },
36
+ * { language: 'javascript', class: 'js', label: 'JavaScript' },
37
+ * };
38
+ *
39
+ * getPropertyAssociation( definitions, 'class', 'language' );
40
+ * ```
41
+ *
42
+ * returns:
43
+ *
44
+ * ```ts
45
+ * {
46
+ * 'language-php': 'php',
47
+ * 'js': 'javascript'
48
+ * }
49
+ * ```
50
+ *
51
+ * and
52
+ *
53
+ * ```ts
54
+ * getPropertyAssociation( definitions, 'language', 'label' );
55
+ * ```
56
+ *
57
+ * returns:
58
+ *
59
+ * ```ts
60
+ * {
61
+ * 'php': 'PHP',
62
+ * 'javascript': 'JavaScript'
63
+ * }
64
+ * ```
65
+ */
66
+ export function getPropertyAssociation(languageDefs, key, value) {
67
+ const association = {};
68
+ for (const def of languageDefs) {
69
+ if (key === 'class') {
70
+ // Only the first class is considered.
71
+ const newKey = (def[key]).split(' ').shift();
72
+ association[newKey] = def[value];
73
+ }
74
+ else {
75
+ association[def[key]] = def[value];
76
+ }
77
+ }
78
+ return association;
79
+ }
80
+ /**
81
+ * For a given model text node, it returns white spaces that precede other characters in that node.
82
+ * This corresponds to the indentation part of the code block line.
83
+ */
84
+ export function getLeadingWhiteSpaces(textNode) {
85
+ return textNode.data.match(/^(\s*)/)[0];
86
+ }
87
+ /**
88
+ * For plain text containing the code (a snippet), it returns a document fragment containing
89
+ * view text nodes separated by `<br>` elements (in place of new line characters "\n"), for instance:
90
+ *
91
+ * Input:
92
+ *
93
+ * ```ts
94
+ * "foo()\n
95
+ * bar()"
96
+ * ```
97
+ *
98
+ * Output:
99
+ *
100
+ * ```html
101
+ * <DocumentFragment>
102
+ * "foo()"
103
+ * <br/>
104
+ * "bar()"
105
+ * </DocumentFragment>
106
+ * ```
107
+ *
108
+ * @param text The raw code text to be converted.
109
+ */
110
+ export function rawSnippetTextToViewDocumentFragment(writer, text) {
111
+ const fragment = writer.createDocumentFragment();
112
+ const textLines = text.split('\n');
113
+ const items = textLines.reduce((nodes, line, lineIndex) => {
114
+ nodes.push(line);
115
+ if (lineIndex < textLines.length - 1) {
116
+ nodes.push(writer.createElement('br'));
117
+ }
118
+ return nodes;
119
+ }, []);
120
+ writer.appendChild(items, fragment);
121
+ return fragment;
122
+ }
123
+ /**
124
+ * Returns an array of all model positions within the selection that represent code block lines.
125
+ *
126
+ * If the selection is collapsed, it returns the exact selection anchor position:
127
+ *
128
+ * ```html
129
+ * <codeBlock>[]foo</codeBlock> -> <codeBlock>^foo</codeBlock>
130
+ * <codeBlock>foo[]bar</codeBlock> -> <codeBlock>foo^bar</codeBlock>
131
+ * ```
132
+ *
133
+ * Otherwise, it returns positions **before** each text node belonging to all code blocks contained by the selection:
134
+ *
135
+ * ```html
136
+ * <codeBlock> <codeBlock>
137
+ * foo[bar ^foobar
138
+ * <softBreak></softBreak> -> <softBreak></softBreak>
139
+ * baz]qux ^bazqux
140
+ * </codeBlock> </codeBlock>
141
+ * ```
142
+ *
143
+ * It also works across other non–code blocks:
144
+ *
145
+ * ```html
146
+ * <codeBlock> <codeBlock>
147
+ * foo[bar ^foobar
148
+ * </codeBlock> </codeBlock>
149
+ * <paragraph>text</paragraph> -> <paragraph>text</paragraph>
150
+ * <codeBlock> <codeBlock>
151
+ * baz]qux ^bazqux
152
+ * </codeBlock> </codeBlock>
153
+ * ```
154
+ *
155
+ * **Note:** The positions are in reverse order so they do not get outdated when iterating over them and
156
+ * the writer inserts or removes elements at the same time.
157
+ *
158
+ * **Note:** The position is located after the leading white spaces in the text node.
159
+ */
160
+ export function getIndentOutdentPositions(model) {
161
+ const selection = model.document.selection;
162
+ const positions = [];
163
+ // When the selection is collapsed, there's only one position we can indent or outdent.
164
+ if (selection.isCollapsed) {
165
+ return [selection.anchor];
166
+ }
167
+ // When the selection is NOT collapsed, collect all positions starting before text nodes
168
+ // (code lines) in any <codeBlock> within the selection.
169
+ // Walk backward so positions we are about to collect here do not get outdated when
170
+ // inserting or deleting using the writer.
171
+ const walker = selection.getFirstRange().getWalker({
172
+ ignoreElementEnd: true,
173
+ direction: 'backward'
174
+ });
175
+ for (const { item } of walker) {
176
+ if (!item.is('$textProxy')) {
177
+ continue;
178
+ }
179
+ const { parent, startOffset } = item.textNode;
180
+ if (!parent.is('element', 'codeBlock')) {
181
+ continue;
182
+ }
183
+ const leadingWhiteSpaces = getLeadingWhiteSpaces(item.textNode);
184
+ // Make sure the position is after all leading whitespaces in the text node.
185
+ const position = model.createPositionAt(parent, startOffset + leadingWhiteSpaces.length);
186
+ positions.push(position);
187
+ }
188
+ return positions;
189
+ }
190
+ /**
191
+ * Checks if any of the blocks within the model selection is a code block.
192
+ */
193
+ export function isModelSelectionInCodeBlock(selection) {
194
+ const firstBlock = first(selection.getSelectedBlocks());
195
+ return !!firstBlock && firstBlock.is('element', 'codeBlock');
196
+ }
197
+ /**
198
+ * Checks if an {@link module:engine/model/element~Element Element} can become a code block.
199
+ *
200
+ * @param schema Model's schema.
201
+ * @param element The element to be checked.
202
+ * @returns Check result.
203
+ */
204
+ export function canBeCodeBlock(schema, element) {
205
+ if (element.is('rootElement') || schema.isLimit(element)) {
206
+ return false;
207
+ }
208
+ return schema.checkChild(element.parent, 'codeBlock');
209
+ }