@ckeditor/ckeditor5-source-editing 0.0.0-nightly-20240423.0 → 0.0.0-nightly-20240425.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.
Files changed (147) hide show
  1. package/dist/index-content.css +4 -0
  2. package/dist/index-editor.css +43 -0
  3. package/dist/index.css +87 -0
  4. package/dist/index.css.map +1 -0
  5. package/dist/index.js +583 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/translations/ar.d.ts +8 -0
  8. package/dist/translations/ar.js +5 -0
  9. package/dist/translations/ar.umd.js +11 -0
  10. package/dist/translations/bg.d.ts +8 -0
  11. package/dist/translations/bg.js +5 -0
  12. package/dist/translations/bg.umd.js +11 -0
  13. package/dist/translations/bn.d.ts +8 -0
  14. package/dist/translations/bn.js +5 -0
  15. package/dist/translations/bn.umd.js +11 -0
  16. package/dist/translations/ca.d.ts +8 -0
  17. package/dist/translations/ca.js +5 -0
  18. package/dist/translations/ca.umd.js +11 -0
  19. package/dist/translations/cs.d.ts +8 -0
  20. package/dist/translations/cs.js +5 -0
  21. package/dist/translations/cs.umd.js +11 -0
  22. package/dist/translations/da.d.ts +8 -0
  23. package/dist/translations/da.js +5 -0
  24. package/dist/translations/da.umd.js +11 -0
  25. package/dist/translations/de.d.ts +8 -0
  26. package/dist/translations/de.js +5 -0
  27. package/dist/translations/de.umd.js +11 -0
  28. package/dist/translations/el.d.ts +8 -0
  29. package/dist/translations/el.js +5 -0
  30. package/dist/translations/el.umd.js +11 -0
  31. package/dist/translations/en-au.d.ts +8 -0
  32. package/dist/translations/en-au.js +5 -0
  33. package/dist/translations/en-au.umd.js +11 -0
  34. package/dist/translations/en.d.ts +8 -0
  35. package/dist/translations/en.js +5 -0
  36. package/dist/translations/en.umd.js +11 -0
  37. package/dist/translations/es.d.ts +8 -0
  38. package/dist/translations/es.js +5 -0
  39. package/dist/translations/es.umd.js +11 -0
  40. package/dist/translations/et.d.ts +8 -0
  41. package/dist/translations/et.js +5 -0
  42. package/dist/translations/et.umd.js +11 -0
  43. package/dist/translations/fi.d.ts +8 -0
  44. package/dist/translations/fi.js +5 -0
  45. package/dist/translations/fi.umd.js +11 -0
  46. package/dist/translations/fr.d.ts +8 -0
  47. package/dist/translations/fr.js +5 -0
  48. package/dist/translations/fr.umd.js +11 -0
  49. package/dist/translations/gl.d.ts +8 -0
  50. package/dist/translations/gl.js +5 -0
  51. package/dist/translations/gl.umd.js +11 -0
  52. package/dist/translations/he.d.ts +8 -0
  53. package/dist/translations/he.js +5 -0
  54. package/dist/translations/he.umd.js +11 -0
  55. package/dist/translations/hi.d.ts +8 -0
  56. package/dist/translations/hi.js +5 -0
  57. package/dist/translations/hi.umd.js +11 -0
  58. package/dist/translations/hr.d.ts +8 -0
  59. package/dist/translations/hr.js +5 -0
  60. package/dist/translations/hr.umd.js +11 -0
  61. package/dist/translations/hu.d.ts +8 -0
  62. package/dist/translations/hu.js +5 -0
  63. package/dist/translations/hu.umd.js +11 -0
  64. package/dist/translations/id.d.ts +8 -0
  65. package/dist/translations/id.js +5 -0
  66. package/dist/translations/id.umd.js +11 -0
  67. package/dist/translations/it.d.ts +8 -0
  68. package/dist/translations/it.js +5 -0
  69. package/dist/translations/it.umd.js +11 -0
  70. package/dist/translations/ja.d.ts +8 -0
  71. package/dist/translations/ja.js +5 -0
  72. package/dist/translations/ja.umd.js +11 -0
  73. package/dist/translations/ko.d.ts +8 -0
  74. package/dist/translations/ko.js +5 -0
  75. package/dist/translations/ko.umd.js +11 -0
  76. package/dist/translations/lt.d.ts +8 -0
  77. package/dist/translations/lt.js +5 -0
  78. package/dist/translations/lt.umd.js +11 -0
  79. package/dist/translations/lv.d.ts +8 -0
  80. package/dist/translations/lv.js +5 -0
  81. package/dist/translations/lv.umd.js +11 -0
  82. package/dist/translations/ms.d.ts +8 -0
  83. package/dist/translations/ms.js +5 -0
  84. package/dist/translations/ms.umd.js +11 -0
  85. package/dist/translations/nl.d.ts +8 -0
  86. package/dist/translations/nl.js +5 -0
  87. package/dist/translations/nl.umd.js +11 -0
  88. package/dist/translations/no.d.ts +8 -0
  89. package/dist/translations/no.js +5 -0
  90. package/dist/translations/no.umd.js +11 -0
  91. package/dist/translations/pl.d.ts +8 -0
  92. package/dist/translations/pl.js +5 -0
  93. package/dist/translations/pl.umd.js +11 -0
  94. package/dist/translations/pt-br.d.ts +8 -0
  95. package/dist/translations/pt-br.js +5 -0
  96. package/dist/translations/pt-br.umd.js +11 -0
  97. package/dist/translations/pt.d.ts +8 -0
  98. package/dist/translations/pt.js +5 -0
  99. package/dist/translations/pt.umd.js +11 -0
  100. package/dist/translations/ro.d.ts +8 -0
  101. package/dist/translations/ro.js +5 -0
  102. package/dist/translations/ro.umd.js +11 -0
  103. package/dist/translations/ru.d.ts +8 -0
  104. package/dist/translations/ru.js +5 -0
  105. package/dist/translations/ru.umd.js +11 -0
  106. package/dist/translations/sk.d.ts +8 -0
  107. package/dist/translations/sk.js +5 -0
  108. package/dist/translations/sk.umd.js +11 -0
  109. package/dist/translations/sr-latn.d.ts +8 -0
  110. package/dist/translations/sr-latn.js +5 -0
  111. package/dist/translations/sr-latn.umd.js +11 -0
  112. package/dist/translations/sr.d.ts +8 -0
  113. package/dist/translations/sr.js +5 -0
  114. package/dist/translations/sr.umd.js +11 -0
  115. package/dist/translations/sv.d.ts +8 -0
  116. package/dist/translations/sv.js +5 -0
  117. package/dist/translations/sv.umd.js +11 -0
  118. package/dist/translations/th.d.ts +8 -0
  119. package/dist/translations/th.js +5 -0
  120. package/dist/translations/th.umd.js +11 -0
  121. package/dist/translations/tr.d.ts +8 -0
  122. package/dist/translations/tr.js +5 -0
  123. package/dist/translations/tr.umd.js +11 -0
  124. package/dist/translations/ug.d.ts +8 -0
  125. package/dist/translations/ug.js +5 -0
  126. package/dist/translations/ug.umd.js +11 -0
  127. package/dist/translations/uk.d.ts +8 -0
  128. package/dist/translations/uk.js +5 -0
  129. package/dist/translations/uk.umd.js +11 -0
  130. package/dist/translations/ur.d.ts +8 -0
  131. package/dist/translations/ur.js +5 -0
  132. package/dist/translations/ur.umd.js +11 -0
  133. package/dist/translations/vi.d.ts +8 -0
  134. package/dist/translations/vi.js +5 -0
  135. package/dist/translations/vi.umd.js +11 -0
  136. package/dist/translations/zh-cn.d.ts +8 -0
  137. package/dist/translations/zh-cn.js +5 -0
  138. package/dist/translations/zh-cn.umd.js +11 -0
  139. package/dist/translations/zh.d.ts +8 -0
  140. package/dist/translations/zh.js +5 -0
  141. package/dist/translations/zh.umd.js +11 -0
  142. package/dist/types/augmentation.d.ts +22 -0
  143. package/dist/types/index.d.ts +14 -0
  144. package/dist/types/sourceediting.d.ts +108 -0
  145. package/dist/types/sourceeditingconfig.d.ts +38 -0
  146. package/dist/types/utils/formathtml.d.ts +23 -0
  147. package/package.json +4 -3
package/dist/index.js ADDED
@@ -0,0 +1,583 @@
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-oss-license
4
+ */
5
+ import { Plugin, PendingActions } from '@ckeditor/ckeditor5-core/dist/index.js';
6
+ import { ButtonView, MenuBarMenuListItemButtonView } from '@ckeditor/ckeditor5-ui/dist/index.js';
7
+ import { CKEditorError, createElement, ElementReplacer } from '@ckeditor/ckeditor5-utils/dist/index.js';
8
+
9
+ /**
10
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
11
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
12
+ */ /**
13
+ * @module source-editing/utils/formathtml
14
+ */ /**
15
+ * A simple (and naive) HTML code formatter that returns a formatted HTML markup that can be easily
16
+ * parsed by human eyes. It beautifies the HTML code by adding new lines between elements that behave like block elements
17
+ * (https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements
18
+ * and a few more like `tr`, `td`, and similar ones) and inserting indents for nested content.
19
+ *
20
+ * WARNING: This function works only on a text that does not contain any indentations or new lines.
21
+ * Calling this function on the already formatted text will damage the formatting.
22
+ *
23
+ * @param input An HTML string to format.
24
+ */ function formatHtml(input) {
25
+ // A list of block-like elements around which the new lines should be inserted, and within which
26
+ // the indentation of their children should be increased.
27
+ // The list is partially based on https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements that contains
28
+ // a full list of HTML block-level elements.
29
+ // A void element is an element that cannot have any child - https://html.spec.whatwg.org/multipage/syntax.html#void-elements.
30
+ // Note that <pre> element is not listed on this list to avoid breaking whitespace formatting.
31
+ // Note that <br> element is not listed and handled separately so no additional white spaces are injected.
32
+ const elementsToFormat = [
33
+ {
34
+ name: 'address',
35
+ isVoid: false
36
+ },
37
+ {
38
+ name: 'article',
39
+ isVoid: false
40
+ },
41
+ {
42
+ name: 'aside',
43
+ isVoid: false
44
+ },
45
+ {
46
+ name: 'blockquote',
47
+ isVoid: false
48
+ },
49
+ {
50
+ name: 'details',
51
+ isVoid: false
52
+ },
53
+ {
54
+ name: 'dialog',
55
+ isVoid: false
56
+ },
57
+ {
58
+ name: 'dd',
59
+ isVoid: false
60
+ },
61
+ {
62
+ name: 'div',
63
+ isVoid: false
64
+ },
65
+ {
66
+ name: 'dl',
67
+ isVoid: false
68
+ },
69
+ {
70
+ name: 'dt',
71
+ isVoid: false
72
+ },
73
+ {
74
+ name: 'fieldset',
75
+ isVoid: false
76
+ },
77
+ {
78
+ name: 'figcaption',
79
+ isVoid: false
80
+ },
81
+ {
82
+ name: 'figure',
83
+ isVoid: false
84
+ },
85
+ {
86
+ name: 'footer',
87
+ isVoid: false
88
+ },
89
+ {
90
+ name: 'form',
91
+ isVoid: false
92
+ },
93
+ {
94
+ name: 'h1',
95
+ isVoid: false
96
+ },
97
+ {
98
+ name: 'h2',
99
+ isVoid: false
100
+ },
101
+ {
102
+ name: 'h3',
103
+ isVoid: false
104
+ },
105
+ {
106
+ name: 'h4',
107
+ isVoid: false
108
+ },
109
+ {
110
+ name: 'h5',
111
+ isVoid: false
112
+ },
113
+ {
114
+ name: 'h6',
115
+ isVoid: false
116
+ },
117
+ {
118
+ name: 'header',
119
+ isVoid: false
120
+ },
121
+ {
122
+ name: 'hgroup',
123
+ isVoid: false
124
+ },
125
+ {
126
+ name: 'hr',
127
+ isVoid: true
128
+ },
129
+ {
130
+ name: 'li',
131
+ isVoid: false
132
+ },
133
+ {
134
+ name: 'main',
135
+ isVoid: false
136
+ },
137
+ {
138
+ name: 'nav',
139
+ isVoid: false
140
+ },
141
+ {
142
+ name: 'ol',
143
+ isVoid: false
144
+ },
145
+ {
146
+ name: 'p',
147
+ isVoid: false
148
+ },
149
+ {
150
+ name: 'section',
151
+ isVoid: false
152
+ },
153
+ {
154
+ name: 'table',
155
+ isVoid: false
156
+ },
157
+ {
158
+ name: 'tbody',
159
+ isVoid: false
160
+ },
161
+ {
162
+ name: 'td',
163
+ isVoid: false
164
+ },
165
+ {
166
+ name: 'th',
167
+ isVoid: false
168
+ },
169
+ {
170
+ name: 'thead',
171
+ isVoid: false
172
+ },
173
+ {
174
+ name: 'tr',
175
+ isVoid: false
176
+ },
177
+ {
178
+ name: 'ul',
179
+ isVoid: false
180
+ }
181
+ ];
182
+ const elementNamesToFormat = elementsToFormat.map((element)=>element.name).join('|');
183
+ // It is not the fastest way to format the HTML markup but the performance should be good enough.
184
+ const lines = input// Add new line before and after `<tag>` and `</tag>`.
185
+ // It may separate individual elements with two new lines, but this will be fixed below.
186
+ .replace(new RegExp(`</?(${elementNamesToFormat})( .*?)?>`, 'g'), '\n$&\n')// Keep `<br>`s at the end of line to avoid adding additional whitespaces before `<br>`.
187
+ .replace(/<br[^>]*>/g, '$&\n')// Divide input string into lines, which start with either an opening tag, a closing tag, or just a text.
188
+ .split('\n');
189
+ let indentCount = 0;
190
+ let isPreformattedLine = false;
191
+ return lines.filter((line)=>line.length).map((line)=>{
192
+ isPreformattedLine = isPreformattedBlockLine(line, isPreformattedLine);
193
+ if (isNonVoidOpeningTag(line, elementsToFormat)) {
194
+ return indentLine(line, indentCount++);
195
+ }
196
+ if (isClosingTag(line, elementsToFormat)) {
197
+ return indentLine(line, --indentCount);
198
+ }
199
+ if (isPreformattedLine === 'middle' || isPreformattedLine === 'last') {
200
+ return line;
201
+ }
202
+ return indentLine(line, indentCount);
203
+ }).join('\n');
204
+ }
205
+ /**
206
+ * Checks, if an argument is an opening tag of a non-void element to be formatted.
207
+ *
208
+ * @param line String to check.
209
+ * @param elementsToFormat Elements to be formatted.
210
+ */ function isNonVoidOpeningTag(line, elementsToFormat) {
211
+ return elementsToFormat.some((element)=>{
212
+ if (element.isVoid) {
213
+ return false;
214
+ }
215
+ if (!new RegExp(`<${element.name}( .*?)?>`).test(line)) {
216
+ return false;
217
+ }
218
+ return true;
219
+ });
220
+ }
221
+ /**
222
+ * Checks, if an argument is a closing tag.
223
+ *
224
+ * @param line String to check.
225
+ * @param elementsToFormat Elements to be formatted.
226
+ */ function isClosingTag(line, elementsToFormat) {
227
+ return elementsToFormat.some((element)=>{
228
+ return new RegExp(`</${element.name}>`).test(line);
229
+ });
230
+ }
231
+ /**
232
+ * Indents a line by a specified number of characters.
233
+ *
234
+ * @param line Line to indent.
235
+ * @param indentCount Number of characters to use for indentation.
236
+ * @param indentChar Indentation character(s). 4 spaces by default.
237
+ */ function indentLine(line, indentCount, indentChar = ' ') {
238
+ // More about Math.max() here in https://github.com/ckeditor/ckeditor5/issues/10698.
239
+ return `${indentChar.repeat(Math.max(0, indentCount))}${line}`;
240
+ }
241
+ /**
242
+ * Checks whether a line belongs to a preformatted (`<pre>`) block.
243
+ *
244
+ * @param line Line to check.
245
+ * @param isPreviousLinePreFormatted Information on whether the previous line was preformatted (and how).
246
+ */ function isPreformattedBlockLine(line, isPreviousLinePreFormatted) {
247
+ if (new RegExp('<pre( .*?)?>').test(line)) {
248
+ return 'first';
249
+ } else if (new RegExp('</pre>').test(line)) {
250
+ return 'last';
251
+ } else if (isPreviousLinePreFormatted === 'first' || isPreviousLinePreFormatted === 'middle') {
252
+ return 'middle';
253
+ } else {
254
+ return false;
255
+ }
256
+ }
257
+
258
+ var sourceEditingIcon = "<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m12.5 0 5 4.5v15.003h-16V0h11zM3 1.5v3.25l-1.497 1-.003 8 1.5 1v3.254L7.685 18l-.001 1.504H17.5V8.002L16 9.428l-.004-4.22-4.222-3.692L3 1.5z\"/><path d=\"M4.06 6.64a.75.75 0 0 1 .958 1.15l-.085.07L2.29 9.75l2.646 1.89c.302.216.4.62.232.951l-.058.095a.75.75 0 0 1-.951.232l-.095-.058-3.5-2.5V9.14l3.496-2.5zm4.194 6.22a.75.75 0 0 1-.958-1.149l.085-.07 2.643-1.89-2.646-1.89a.75.75 0 0 1-.232-.952l.058-.095a.75.75 0 0 1 .95-.232l.096.058 3.5 2.5v1.22l-3.496 2.5zm7.644-.836 2.122 2.122-5.825 5.809-2.125-.005.003-2.116zm2.539-1.847 1.414 1.414a.5.5 0 0 1 0 .707l-1.06 1.06-2.122-2.12 1.061-1.061a.5.5 0 0 1 .707 0z\"/></svg>";
259
+
260
+ const COMMAND_FORCE_DISABLE_ID = 'SourceEditingMode';
261
+ class SourceEditing extends Plugin {
262
+ /**
263
+ * @inheritDoc
264
+ */ static get pluginName() {
265
+ return 'SourceEditing';
266
+ }
267
+ /**
268
+ * @inheritDoc
269
+ */ static get requires() {
270
+ return [
271
+ PendingActions
272
+ ];
273
+ }
274
+ /**
275
+ * @inheritDoc
276
+ */ init() {
277
+ this._checkCompatibility();
278
+ const editor = this.editor;
279
+ const t = editor.locale.t;
280
+ editor.ui.componentFactory.add('sourceEditing', ()=>{
281
+ const buttonView = this._createButton(ButtonView);
282
+ buttonView.set({
283
+ label: t('Source'),
284
+ icon: sourceEditingIcon,
285
+ tooltip: true,
286
+ class: 'ck-source-editing-button'
287
+ });
288
+ return buttonView;
289
+ });
290
+ editor.ui.componentFactory.add('menuBar:sourceEditing', ()=>{
291
+ const buttonView = this._createButton(MenuBarMenuListItemButtonView);
292
+ buttonView.set({
293
+ label: t('Show source')
294
+ });
295
+ return buttonView;
296
+ });
297
+ // Currently, the plugin handles the source editing mode by itself only for the classic editor. To use this plugin with other
298
+ // integrations, listen to the `change:isSourceEditingMode` event and act accordingly.
299
+ if (this._isAllowedToHandleSourceEditingMode()) {
300
+ this.on('change:isSourceEditingMode', (evt, name, isSourceEditingMode)=>{
301
+ if (isSourceEditingMode) {
302
+ this._hideVisibleDialog();
303
+ this._showSourceEditing();
304
+ this._disableCommands();
305
+ } else {
306
+ this._hideSourceEditing();
307
+ this._enableCommands();
308
+ }
309
+ });
310
+ this.on('change:isEnabled', (evt, name, isEnabled)=>this._handleReadOnlyMode(!isEnabled));
311
+ this.listenTo(editor, 'change:isReadOnly', (evt, name, isReadOnly)=>this._handleReadOnlyMode(isReadOnly));
312
+ }
313
+ // Update the editor data while calling editor.getData() in the source editing mode.
314
+ editor.data.on('get', ()=>{
315
+ if (this.isSourceEditingMode) {
316
+ this.updateEditorData();
317
+ }
318
+ }, {
319
+ priority: 'high'
320
+ });
321
+ }
322
+ /**
323
+ * Updates the source data in all hidden editing roots.
324
+ */ updateEditorData() {
325
+ const editor = this.editor;
326
+ const data = {};
327
+ for (const [rootName, domSourceEditingElementWrapper] of this._replacedRoots){
328
+ const oldData = this._dataFromRoots.get(rootName);
329
+ const newData = domSourceEditingElementWrapper.dataset.value;
330
+ // Do not set the data unless some changes have been made in the meantime.
331
+ // This prevents empty undo steps after switching to the normal editor.
332
+ if (oldData !== newData) {
333
+ data[rootName] = newData;
334
+ this._dataFromRoots.set(rootName, newData);
335
+ }
336
+ }
337
+ if (Object.keys(data).length) {
338
+ editor.data.set(data, {
339
+ batchType: {
340
+ isUndoable: true
341
+ },
342
+ suppressErrorInCollaboration: true
343
+ });
344
+ }
345
+ }
346
+ _checkCompatibility() {
347
+ const editor = this.editor;
348
+ const allowCollaboration = editor.config.get('sourceEditing.allowCollaborationFeatures');
349
+ if (!allowCollaboration && editor.plugins.has('RealTimeCollaborativeEditing')) {
350
+ /**
351
+ * Source editing feature is not fully compatible with real-time collaboration,
352
+ * and using it may lead to data loss. Please read
353
+ * {@glink features/source-editing#limitations-and-incompatibilities source editing feature guide} to learn more.
354
+ *
355
+ * If you understand the possible risk of data loss, you can enable the source editing
356
+ * by setting the
357
+ * {@link module:source-editing/sourceeditingconfig~SourceEditingConfig#allowCollaborationFeatures}
358
+ * configuration flag to `true`.
359
+ *
360
+ * @error source-editing-incompatible-with-real-time-collaboration
361
+ */ throw new CKEditorError('source-editing-incompatible-with-real-time-collaboration', null);
362
+ }
363
+ const collaborationPluginNamesToWarn = [
364
+ 'CommentsEditing',
365
+ 'TrackChangesEditing',
366
+ 'RevisionHistory'
367
+ ];
368
+ // Currently, the basic integration with Collaboration Features is to display a warning in the console.
369
+ //
370
+ // If `allowCollaboration` flag is set, do not show these warnings. If the flag is set, we assume that the integrator read
371
+ // appropriate section of the guide so there's no use to spam the console with warnings.
372
+ //
373
+ if (!allowCollaboration && collaborationPluginNamesToWarn.some((pluginName)=>editor.plugins.has(pluginName))) {
374
+ console.warn('You initialized the editor with the source editing feature and at least one of the collaboration features. ' + 'Please be advised that the source editing feature may not work, and be careful when editing document source ' + 'that contains markers created by the collaboration features.');
375
+ }
376
+ // Restricted Editing integration can also lead to problems. Warn the user accordingly.
377
+ if (editor.plugins.has('RestrictedEditingModeEditing')) {
378
+ console.warn('You initialized the editor with the source editing feature and restricted editing feature. ' + 'Please be advised that the source editing feature may not work, and be careful when editing document source ' + 'that contains markers created by the restricted editing feature.');
379
+ }
380
+ }
381
+ /**
382
+ * Creates source editing wrappers that replace each editing root. Each wrapper contains the document source from the corresponding
383
+ * root.
384
+ *
385
+ * The wrapper element contains a textarea and it solves the problem, that the textarea element cannot auto expand its height based on
386
+ * the content it contains. The solution is to make the textarea more like a plain div element, which expands in height as much as it
387
+ * needs to, in order to display the whole document source without scrolling. The wrapper element is a parent for the textarea and for
388
+ * the pseudo-element `::after`, that replicates the look, content, and position of the textarea. The pseudo-element replica is hidden,
389
+ * but it is styled to be an identical visual copy of the textarea with the same content. Then, the wrapper is a grid container and both
390
+ * of its children (the textarea and the `::after` pseudo-element) are positioned within a CSS grid to occupy the same grid cell. The
391
+ * content in the pseudo-element `::after` is set in CSS and it stretches the grid to the appropriate size based on the textarea value.
392
+ * Since both children occupy the same grid cell, both have always the same height.
393
+ */ _showSourceEditing() {
394
+ const editor = this.editor;
395
+ const editingView = editor.editing.view;
396
+ const model = editor.model;
397
+ model.change((writer)=>{
398
+ writer.setSelection(null);
399
+ writer.removeSelectionAttribute(model.document.selection.getAttributeKeys());
400
+ });
401
+ // It is not needed to iterate through all editing roots, as currently the plugin supports only the Classic Editor with a single
402
+ // main root, but this code may help understand and use this feature in external integrations.
403
+ for (const [rootName, domRootElement] of editingView.domRoots){
404
+ const data = formatSource(editor.data.get({
405
+ rootName
406
+ }));
407
+ const domSourceEditingElementTextarea = createElement(domRootElement.ownerDocument, 'textarea', {
408
+ rows: '1',
409
+ 'aria-label': 'Source code editing area'
410
+ });
411
+ const domSourceEditingElementWrapper = createElement(domRootElement.ownerDocument, 'div', {
412
+ class: 'ck-source-editing-area',
413
+ 'data-value': data
414
+ }, [
415
+ domSourceEditingElementTextarea
416
+ ]);
417
+ domSourceEditingElementTextarea.value = data;
418
+ // Setting a value to textarea moves the input cursor to the end. We want the selection at the beginning.
419
+ domSourceEditingElementTextarea.setSelectionRange(0, 0);
420
+ // Bind the textarea's value to the wrapper's `data-value` property. Each change of the textarea's value updates the
421
+ // wrapper's `data-value` property.
422
+ domSourceEditingElementTextarea.addEventListener('input', ()=>{
423
+ domSourceEditingElementWrapper.dataset.value = domSourceEditingElementTextarea.value;
424
+ editor.ui.update();
425
+ });
426
+ editingView.change((writer)=>{
427
+ const viewRoot = editingView.document.getRoot(rootName);
428
+ writer.addClass('ck-hidden', viewRoot);
429
+ });
430
+ // Register the element so it becomes available for Alt+F10 and Esc navigation.
431
+ editor.ui.setEditableElement('sourceEditing:' + rootName, domSourceEditingElementTextarea);
432
+ this._replacedRoots.set(rootName, domSourceEditingElementWrapper);
433
+ this._elementReplacer.replace(domRootElement, domSourceEditingElementWrapper);
434
+ this._dataFromRoots.set(rootName, data);
435
+ }
436
+ this._focusSourceEditing();
437
+ }
438
+ /**
439
+ * Restores all hidden editing roots and sets the source data in them.
440
+ */ _hideSourceEditing() {
441
+ const editor = this.editor;
442
+ const editingView = editor.editing.view;
443
+ this.updateEditorData();
444
+ editingView.change((writer)=>{
445
+ for (const [rootName] of this._replacedRoots){
446
+ writer.removeClass('ck-hidden', editingView.document.getRoot(rootName));
447
+ }
448
+ });
449
+ this._elementReplacer.restore();
450
+ this._replacedRoots.clear();
451
+ this._dataFromRoots.clear();
452
+ editingView.focus();
453
+ }
454
+ /**
455
+ * Focuses the textarea containing document source from the first editing root.
456
+ */ _focusSourceEditing() {
457
+ const editor = this.editor;
458
+ const [domSourceEditingElementWrapper] = this._replacedRoots.values();
459
+ const textarea = domSourceEditingElementWrapper.querySelector('textarea');
460
+ // The FocusObserver was disabled by View.render() while the DOM root was getting hidden and the replacer
461
+ // revealed the textarea. So it couldn't notice that the DOM root got blurred in the process.
462
+ // Let's sync this state manually here because otherwise Renderer will attempt to render selection
463
+ // in an invisible DOM root.
464
+ editor.editing.view.document.isFocused = false;
465
+ textarea.focus();
466
+ }
467
+ /**
468
+ * Disables all commands.
469
+ */ _disableCommands() {
470
+ const editor = this.editor;
471
+ for (const command of editor.commands.commands()){
472
+ command.forceDisabled(COMMAND_FORCE_DISABLE_ID);
473
+ }
474
+ // Comments archive UI plugin will be disabled manually too.
475
+ if (editor.plugins.has('CommentsArchiveUI')) {
476
+ editor.plugins.get('CommentsArchiveUI').forceDisabled(COMMAND_FORCE_DISABLE_ID);
477
+ }
478
+ }
479
+ /**
480
+ * Clears forced disable for all commands, that was previously set through {@link #_disableCommands}.
481
+ */ _enableCommands() {
482
+ const editor = this.editor;
483
+ for (const command of editor.commands.commands()){
484
+ command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
485
+ }
486
+ // Comments archive UI plugin will be enabled manually too.
487
+ if (editor.plugins.has('CommentsArchiveUI')) {
488
+ editor.plugins.get('CommentsArchiveUI').clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
489
+ }
490
+ }
491
+ /**
492
+ * Adds or removes the `readonly` attribute from the textarea from all roots, if document source mode is active.
493
+ *
494
+ * @param isReadOnly Indicates whether all textarea elements should be read-only.
495
+ */ _handleReadOnlyMode(isReadOnly) {
496
+ if (!this.isSourceEditingMode) {
497
+ return;
498
+ }
499
+ for (const [, domSourceEditingElementWrapper] of this._replacedRoots){
500
+ domSourceEditingElementWrapper.querySelector('textarea').readOnly = isReadOnly;
501
+ }
502
+ }
503
+ /**
504
+ * Checks, if the plugin is allowed to handle the source editing mode by itself. Currently, the source editing mode is supported only
505
+ * for the {@link module:editor-classic/classiceditor~ClassicEditor classic editor}.
506
+ */ _isAllowedToHandleSourceEditingMode() {
507
+ const editor = this.editor;
508
+ const editable = editor.ui.view.editable;
509
+ // Checks, if the editor's editable belongs to the editor's DOM tree.
510
+ return editable && !editable.hasExternalElement;
511
+ }
512
+ /**
513
+ * If any {@link module:ui/dialog/dialogview~DialogView editor dialog} is currently visible, hide it.
514
+ */ _hideVisibleDialog() {
515
+ if (this.editor.plugins.has('Dialog')) {
516
+ const dialogPlugin = this.editor.plugins.get('Dialog');
517
+ if (dialogPlugin.isOpen) {
518
+ dialogPlugin.hide();
519
+ }
520
+ }
521
+ }
522
+ _createButton(ButtonClass) {
523
+ const editor = this.editor;
524
+ const buttonView = new ButtonClass(editor.locale);
525
+ buttonView.set({
526
+ withText: true
527
+ });
528
+ buttonView.bind('isOn').to(this, 'isSourceEditingMode');
529
+ // The button should be disabled if one of the following conditions is met:
530
+ buttonView.bind('isEnabled').to(this, 'isEnabled', editor, 'isReadOnly', editor.plugins.get(PendingActions), 'hasAny', (isEnabled, isEditorReadOnly, hasAnyPendingActions)=>{
531
+ // (1) The plugin itself is disabled.
532
+ if (!isEnabled) {
533
+ return false;
534
+ }
535
+ // (2) The editor is in read-only mode.
536
+ if (isEditorReadOnly) {
537
+ return false;
538
+ }
539
+ // (3) Any pending action is scheduled. It may change the model, so modifying the document source should be prevented
540
+ // until the model is finally set.
541
+ if (hasAnyPendingActions) {
542
+ return false;
543
+ }
544
+ return true;
545
+ });
546
+ this.listenTo(buttonView, 'execute', ()=>{
547
+ this.isSourceEditingMode = !this.isSourceEditingMode;
548
+ });
549
+ return buttonView;
550
+ }
551
+ /**
552
+ * @inheritDoc
553
+ */ constructor(editor){
554
+ super(editor);
555
+ this.set('isSourceEditingMode', false);
556
+ this._elementReplacer = new ElementReplacer();
557
+ this._replacedRoots = new Map();
558
+ this._dataFromRoots = new Map();
559
+ editor.config.define('sourceEditing.allowCollaborationFeatures', false);
560
+ }
561
+ }
562
+ /**
563
+ * Formats the content for a better readability.
564
+ *
565
+ * For a non-HTML source the unchanged input string is returned.
566
+ *
567
+ * @param input Input string to check.
568
+ */ function formatSource(input) {
569
+ if (!isHtml(input)) {
570
+ return input;
571
+ }
572
+ return formatHtml(input);
573
+ }
574
+ /**
575
+ * Checks, if the document source is HTML. It is sufficient to just check the first character from the document data.
576
+ *
577
+ * @param input Input string to check.
578
+ */ function isHtml(input) {
579
+ return input.startsWith('<');
580
+ }
581
+
582
+ export { SourceEditing };
583
+ //# sourceMappingURL=index.js.map