@ckeditor/ckeditor5-source-editing 39.0.1 → 40.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +3 -3
  3. package/build/source-editing.js +1 -1
  4. package/build/source-editing.js.map +1 -0
  5. package/lang/translations/ar.po +1 -0
  6. package/lang/translations/bg.po +1 -0
  7. package/lang/translations/bn.po +1 -0
  8. package/lang/translations/ca.po +1 -0
  9. package/lang/translations/cs.po +1 -0
  10. package/lang/translations/da.po +1 -0
  11. package/lang/translations/de.po +1 -0
  12. package/lang/translations/el.po +1 -0
  13. package/lang/translations/en-au.po +1 -0
  14. package/lang/translations/en.po +1 -0
  15. package/lang/translations/es.po +1 -0
  16. package/lang/translations/et.po +1 -0
  17. package/lang/translations/fi.po +1 -0
  18. package/lang/translations/fr.po +1 -0
  19. package/lang/translations/gl.po +1 -0
  20. package/lang/translations/he.po +1 -0
  21. package/lang/translations/hi.po +1 -0
  22. package/lang/translations/hr.po +1 -0
  23. package/lang/translations/hu.po +1 -0
  24. package/lang/translations/id.po +1 -0
  25. package/lang/translations/it.po +1 -0
  26. package/lang/translations/ja.po +1 -0
  27. package/lang/translations/ko.po +1 -0
  28. package/lang/translations/lt.po +1 -0
  29. package/lang/translations/lv.po +1 -0
  30. package/lang/translations/ms.po +1 -0
  31. package/lang/translations/nl.po +1 -0
  32. package/lang/translations/no.po +1 -0
  33. package/lang/translations/pl.po +1 -0
  34. package/lang/translations/pt-br.po +1 -0
  35. package/lang/translations/pt.po +1 -0
  36. package/lang/translations/ro.po +1 -0
  37. package/lang/translations/ru.po +1 -0
  38. package/lang/translations/sk.po +1 -0
  39. package/lang/translations/sr-latn.po +1 -0
  40. package/lang/translations/sr.po +1 -0
  41. package/lang/translations/sv.po +1 -0
  42. package/lang/translations/th.po +1 -0
  43. package/lang/translations/tr.po +1 -0
  44. package/lang/translations/ug.po +1 -0
  45. package/lang/translations/uk.po +1 -0
  46. package/lang/translations/ur.po +1 -0
  47. package/lang/translations/vi.po +1 -0
  48. package/lang/translations/zh-cn.po +1 -0
  49. package/lang/translations/zh.po +1 -0
  50. package/package.json +3 -7
  51. package/src/augmentation.d.ts +10 -10
  52. package/src/augmentation.js +5 -5
  53. package/src/index.d.ts +9 -9
  54. package/src/index.js +9 -9
  55. package/src/sourceediting.d.ts +102 -102
  56. package/src/sourceediting.js +299 -299
  57. package/src/utils/formathtml.d.ts +19 -19
  58. package/src/utils/formathtml.js +128 -130
@@ -1,299 +1,299 @@
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
- /**
6
- * @module source-editing/sourceediting
7
- */
8
- /* global console */
9
- import { Plugin, PendingActions } from 'ckeditor5/src/core';
10
- import { ButtonView } from 'ckeditor5/src/ui';
11
- import { createElement, ElementReplacer } from 'ckeditor5/src/utils';
12
- import { formatHtml } from './utils/formathtml';
13
- import '../theme/sourceediting.css';
14
- import sourceEditingIcon from '../theme/icons/source-editing.svg';
15
- const COMMAND_FORCE_DISABLE_ID = 'SourceEditingMode';
16
- /**
17
- * The source editing feature.
18
- *
19
- * It provides the possibility to view and edit the source of the document.
20
- *
21
- * For a detailed overview, check the {@glink features/source-editing source editing feature documentation} and the
22
- * {@glink api/source-editing package page}.
23
- */
24
- export default class SourceEditing extends Plugin {
25
- /**
26
- * @inheritDoc
27
- */
28
- static get pluginName() {
29
- return 'SourceEditing';
30
- }
31
- /**
32
- * @inheritDoc
33
- */
34
- static get requires() {
35
- return [PendingActions];
36
- }
37
- /**
38
- * @inheritDoc
39
- */
40
- constructor(editor) {
41
- super(editor);
42
- this.set('isSourceEditingMode', false);
43
- this._elementReplacer = new ElementReplacer();
44
- this._replacedRoots = new Map();
45
- this._dataFromRoots = new Map();
46
- }
47
- /**
48
- * @inheritDoc
49
- */
50
- init() {
51
- const editor = this.editor;
52
- const t = editor.t;
53
- editor.ui.componentFactory.add('sourceEditing', locale => {
54
- const buttonView = new ButtonView(locale);
55
- buttonView.set({
56
- label: t('Source'),
57
- icon: sourceEditingIcon,
58
- tooltip: true,
59
- withText: true,
60
- class: 'ck-source-editing-button'
61
- });
62
- buttonView.bind('isOn').to(this, 'isSourceEditingMode');
63
- // The button should be disabled if one of the following conditions is met:
64
- buttonView.bind('isEnabled').to(this, 'isEnabled', editor, 'isReadOnly', editor.plugins.get(PendingActions), 'hasAny', (isEnabled, isEditorReadOnly, hasAnyPendingActions) => {
65
- // (1) The plugin itself is disabled.
66
- if (!isEnabled) {
67
- return false;
68
- }
69
- // (2) The editor is in read-only mode.
70
- if (isEditorReadOnly) {
71
- return false;
72
- }
73
- // (3) Any pending action is scheduled. It may change the model, so modifying the document source should be prevented
74
- // until the model is finally set.
75
- if (hasAnyPendingActions) {
76
- return false;
77
- }
78
- return true;
79
- });
80
- this.listenTo(buttonView, 'execute', () => {
81
- this.isSourceEditingMode = !this.isSourceEditingMode;
82
- });
83
- return buttonView;
84
- });
85
- // Currently, the plugin handles the source editing mode by itself only for the classic editor. To use this plugin with other
86
- // integrations, listen to the `change:isSourceEditingMode` event and act accordingly.
87
- if (this._isAllowedToHandleSourceEditingMode()) {
88
- this.on('change:isSourceEditingMode', (evt, name, isSourceEditingMode) => {
89
- if (isSourceEditingMode) {
90
- this._showSourceEditing();
91
- this._disableCommands();
92
- }
93
- else {
94
- this._hideSourceEditing();
95
- this._enableCommands();
96
- }
97
- });
98
- this.on('change:isEnabled', (evt, name, isEnabled) => this._handleReadOnlyMode(!isEnabled));
99
- this.listenTo(editor, 'change:isReadOnly', (evt, name, isReadOnly) => this._handleReadOnlyMode(isReadOnly));
100
- }
101
- // Update the editor data while calling editor.getData() in the source editing mode.
102
- editor.data.on('get', () => {
103
- if (this.isSourceEditingMode) {
104
- this.updateEditorData();
105
- }
106
- }, { priority: 'high' });
107
- }
108
- /**
109
- * @inheritDoc
110
- */
111
- afterInit() {
112
- const editor = this.editor;
113
- const collaborationPluginNamesToWarn = [
114
- 'RealTimeCollaborativeEditing',
115
- 'CommentsEditing',
116
- 'TrackChangesEditing',
117
- 'RevisionHistory'
118
- ];
119
- // Currently, the basic integration with Collaboration Features is to display a warning in the console.
120
- if (collaborationPluginNamesToWarn.some(pluginName => editor.plugins.has(pluginName))) {
121
- console.warn('You initialized the editor with the source editing feature and at least one of the collaboration features. ' +
122
- 'Please be advised that the source editing feature may not work, and be careful when editing document source ' +
123
- 'that contains markers created by the collaboration features.');
124
- }
125
- // Restricted Editing integration can also lead to problems. Warn the user accordingly.
126
- if (editor.plugins.has('RestrictedEditingModeEditing')) {
127
- console.warn('You initialized the editor with the source editing feature and restricted editing feature. ' +
128
- 'Please be advised that the source editing feature may not work, and be careful when editing document source ' +
129
- 'that contains markers created by the restricted editing feature.');
130
- }
131
- }
132
- /**
133
- * Updates the source data in all hidden editing roots.
134
- */
135
- updateEditorData() {
136
- const editor = this.editor;
137
- const data = {};
138
- for (const [rootName, domSourceEditingElementWrapper] of this._replacedRoots) {
139
- const oldData = this._dataFromRoots.get(rootName);
140
- const newData = domSourceEditingElementWrapper.dataset.value;
141
- // Do not set the data unless some changes have been made in the meantime.
142
- // This prevents empty undo steps after switching to the normal editor.
143
- if (oldData !== newData) {
144
- data[rootName] = newData;
145
- this._dataFromRoots.set(rootName, newData);
146
- }
147
- }
148
- if (Object.keys(data).length) {
149
- editor.data.set(data, { batchType: { isUndoable: true } });
150
- }
151
- }
152
- /**
153
- * Creates source editing wrappers that replace each editing root. Each wrapper contains the document source from the corresponding
154
- * root.
155
- *
156
- * The wrapper element contains a textarea and it solves the problem, that the textarea element cannot auto expand its height based on
157
- * 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
158
- * needs to, in order to display the whole document source without scrolling. The wrapper element is a parent for the textarea and for
159
- * the pseudo-element `::after`, that replicates the look, content, and position of the textarea. The pseudo-element replica is hidden,
160
- * 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
161
- * of its children (the textarea and the `::after` pseudo-element) are positioned within a CSS grid to occupy the same grid cell. The
162
- * content in the pseudo-element `::after` is set in CSS and it stretches the grid to the appropriate size based on the textarea value.
163
- * Since both children occupy the same grid cell, both have always the same height.
164
- */
165
- _showSourceEditing() {
166
- const editor = this.editor;
167
- const editingView = editor.editing.view;
168
- const model = editor.model;
169
- model.change(writer => {
170
- writer.setSelection(null);
171
- writer.removeSelectionAttribute(model.document.selection.getAttributeKeys());
172
- });
173
- // It is not needed to iterate through all editing roots, as currently the plugin supports only the Classic Editor with a single
174
- // main root, but this code may help understand and use this feature in external integrations.
175
- for (const [rootName, domRootElement] of editingView.domRoots) {
176
- const data = formatSource(editor.data.get({ rootName }));
177
- const domSourceEditingElementTextarea = createElement(domRootElement.ownerDocument, 'textarea', {
178
- rows: '1',
179
- 'aria-label': 'Source code editing area'
180
- });
181
- const domSourceEditingElementWrapper = createElement(domRootElement.ownerDocument, 'div', {
182
- class: 'ck-source-editing-area',
183
- 'data-value': data
184
- }, [domSourceEditingElementTextarea]);
185
- domSourceEditingElementTextarea.value = data;
186
- // Setting a value to textarea moves the input cursor to the end. We want the selection at the beginning.
187
- domSourceEditingElementTextarea.setSelectionRange(0, 0);
188
- // Bind the textarea's value to the wrapper's `data-value` property. Each change of the textarea's value updates the
189
- // wrapper's `data-value` property.
190
- domSourceEditingElementTextarea.addEventListener('input', () => {
191
- domSourceEditingElementWrapper.dataset.value = domSourceEditingElementTextarea.value;
192
- editor.ui.update();
193
- });
194
- editingView.change(writer => {
195
- const viewRoot = editingView.document.getRoot(rootName);
196
- writer.addClass('ck-hidden', viewRoot);
197
- });
198
- // Register the element so it becomes available for Alt+F10 and Esc navigation.
199
- editor.ui.setEditableElement('sourceEditing:' + rootName, domSourceEditingElementTextarea);
200
- this._replacedRoots.set(rootName, domSourceEditingElementWrapper);
201
- this._elementReplacer.replace(domRootElement, domSourceEditingElementWrapper);
202
- this._dataFromRoots.set(rootName, data);
203
- }
204
- this._focusSourceEditing();
205
- }
206
- /**
207
- * Restores all hidden editing roots and sets the source data in them.
208
- */
209
- _hideSourceEditing() {
210
- const editor = this.editor;
211
- const editingView = editor.editing.view;
212
- this.updateEditorData();
213
- editingView.change(writer => {
214
- for (const [rootName] of this._replacedRoots) {
215
- writer.removeClass('ck-hidden', editingView.document.getRoot(rootName));
216
- }
217
- });
218
- this._elementReplacer.restore();
219
- this._replacedRoots.clear();
220
- this._dataFromRoots.clear();
221
- editingView.focus();
222
- }
223
- /**
224
- * Focuses the textarea containing document source from the first editing root.
225
- */
226
- _focusSourceEditing() {
227
- const editor = this.editor;
228
- const [domSourceEditingElementWrapper] = this._replacedRoots.values();
229
- const textarea = domSourceEditingElementWrapper.querySelector('textarea');
230
- // The FocusObserver was disabled by View.render() while the DOM root was getting hidden and the replacer
231
- // revealed the textarea. So it couldn't notice that the DOM root got blurred in the process.
232
- // Let's sync this state manually here because otherwise Renderer will attempt to render selection
233
- // in an invisible DOM root.
234
- editor.editing.view.document.isFocused = false;
235
- textarea.focus();
236
- }
237
- /**
238
- * Disables all commands.
239
- */
240
- _disableCommands() {
241
- const editor = this.editor;
242
- for (const command of editor.commands.commands()) {
243
- command.forceDisabled(COMMAND_FORCE_DISABLE_ID);
244
- }
245
- }
246
- /**
247
- * Clears forced disable for all commands, that was previously set through {@link #_disableCommands}.
248
- */
249
- _enableCommands() {
250
- const editor = this.editor;
251
- for (const command of editor.commands.commands()) {
252
- command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
253
- }
254
- }
255
- /**
256
- * Adds or removes the `readonly` attribute from the textarea from all roots, if document source mode is active.
257
- *
258
- * @param isReadOnly Indicates whether all textarea elements should be read-only.
259
- */
260
- _handleReadOnlyMode(isReadOnly) {
261
- if (!this.isSourceEditingMode) {
262
- return;
263
- }
264
- for (const [, domSourceEditingElementWrapper] of this._replacedRoots) {
265
- domSourceEditingElementWrapper.querySelector('textarea').readOnly = isReadOnly;
266
- }
267
- }
268
- /**
269
- * Checks, if the plugin is allowed to handle the source editing mode by itself. Currently, the source editing mode is supported only
270
- * for the {@link module:editor-classic/classiceditor~ClassicEditor classic editor}.
271
- */
272
- _isAllowedToHandleSourceEditingMode() {
273
- const editor = this.editor;
274
- const editable = editor.ui.view.editable;
275
- // Checks, if the editor's editable belongs to the editor's DOM tree.
276
- return editable && !editable.hasExternalElement;
277
- }
278
- }
279
- /**
280
- * Formats the content for a better readability.
281
- *
282
- * For a non-HTML source the unchanged input string is returned.
283
- *
284
- * @param input Input string to check.
285
- */
286
- function formatSource(input) {
287
- if (!isHtml(input)) {
288
- return input;
289
- }
290
- return formatHtml(input);
291
- }
292
- /**
293
- * Checks, if the document source is HTML. It is sufficient to just check the first character from the document data.
294
- *
295
- * @param input Input string to check.
296
- */
297
- function isHtml(input) {
298
- return input.startsWith('<');
299
- }
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
+ /**
6
+ * @module source-editing/sourceediting
7
+ */
8
+ /* global console */
9
+ import { Plugin, PendingActions } from 'ckeditor5/src/core';
10
+ import { ButtonView } from 'ckeditor5/src/ui';
11
+ import { createElement, ElementReplacer } from 'ckeditor5/src/utils';
12
+ import { formatHtml } from './utils/formathtml';
13
+ import '../theme/sourceediting.css';
14
+ import sourceEditingIcon from '../theme/icons/source-editing.svg';
15
+ const COMMAND_FORCE_DISABLE_ID = 'SourceEditingMode';
16
+ /**
17
+ * The source editing feature.
18
+ *
19
+ * It provides the possibility to view and edit the source of the document.
20
+ *
21
+ * For a detailed overview, check the {@glink features/source-editing source editing feature documentation} and the
22
+ * {@glink api/source-editing package page}.
23
+ */
24
+ export default class SourceEditing extends Plugin {
25
+ /**
26
+ * @inheritDoc
27
+ */
28
+ static get pluginName() {
29
+ return 'SourceEditing';
30
+ }
31
+ /**
32
+ * @inheritDoc
33
+ */
34
+ static get requires() {
35
+ return [PendingActions];
36
+ }
37
+ /**
38
+ * @inheritDoc
39
+ */
40
+ constructor(editor) {
41
+ super(editor);
42
+ this.set('isSourceEditingMode', false);
43
+ this._elementReplacer = new ElementReplacer();
44
+ this._replacedRoots = new Map();
45
+ this._dataFromRoots = new Map();
46
+ }
47
+ /**
48
+ * @inheritDoc
49
+ */
50
+ init() {
51
+ const editor = this.editor;
52
+ const t = editor.t;
53
+ editor.ui.componentFactory.add('sourceEditing', locale => {
54
+ const buttonView = new ButtonView(locale);
55
+ buttonView.set({
56
+ label: t('Source'),
57
+ icon: sourceEditingIcon,
58
+ tooltip: true,
59
+ withText: true,
60
+ class: 'ck-source-editing-button'
61
+ });
62
+ buttonView.bind('isOn').to(this, 'isSourceEditingMode');
63
+ // The button should be disabled if one of the following conditions is met:
64
+ buttonView.bind('isEnabled').to(this, 'isEnabled', editor, 'isReadOnly', editor.plugins.get(PendingActions), 'hasAny', (isEnabled, isEditorReadOnly, hasAnyPendingActions) => {
65
+ // (1) The plugin itself is disabled.
66
+ if (!isEnabled) {
67
+ return false;
68
+ }
69
+ // (2) The editor is in read-only mode.
70
+ if (isEditorReadOnly) {
71
+ return false;
72
+ }
73
+ // (3) Any pending action is scheduled. It may change the model, so modifying the document source should be prevented
74
+ // until the model is finally set.
75
+ if (hasAnyPendingActions) {
76
+ return false;
77
+ }
78
+ return true;
79
+ });
80
+ this.listenTo(buttonView, 'execute', () => {
81
+ this.isSourceEditingMode = !this.isSourceEditingMode;
82
+ });
83
+ return buttonView;
84
+ });
85
+ // Currently, the plugin handles the source editing mode by itself only for the classic editor. To use this plugin with other
86
+ // integrations, listen to the `change:isSourceEditingMode` event and act accordingly.
87
+ if (this._isAllowedToHandleSourceEditingMode()) {
88
+ this.on('change:isSourceEditingMode', (evt, name, isSourceEditingMode) => {
89
+ if (isSourceEditingMode) {
90
+ this._showSourceEditing();
91
+ this._disableCommands();
92
+ }
93
+ else {
94
+ this._hideSourceEditing();
95
+ this._enableCommands();
96
+ }
97
+ });
98
+ this.on('change:isEnabled', (evt, name, isEnabled) => this._handleReadOnlyMode(!isEnabled));
99
+ this.listenTo(editor, 'change:isReadOnly', (evt, name, isReadOnly) => this._handleReadOnlyMode(isReadOnly));
100
+ }
101
+ // Update the editor data while calling editor.getData() in the source editing mode.
102
+ editor.data.on('get', () => {
103
+ if (this.isSourceEditingMode) {
104
+ this.updateEditorData();
105
+ }
106
+ }, { priority: 'high' });
107
+ }
108
+ /**
109
+ * @inheritDoc
110
+ */
111
+ afterInit() {
112
+ const editor = this.editor;
113
+ const collaborationPluginNamesToWarn = [
114
+ 'RealTimeCollaborativeEditing',
115
+ 'CommentsEditing',
116
+ 'TrackChangesEditing',
117
+ 'RevisionHistory'
118
+ ];
119
+ // Currently, the basic integration with Collaboration Features is to display a warning in the console.
120
+ if (collaborationPluginNamesToWarn.some(pluginName => editor.plugins.has(pluginName))) {
121
+ console.warn('You initialized the editor with the source editing feature and at least one of the collaboration features. ' +
122
+ 'Please be advised that the source editing feature may not work, and be careful when editing document source ' +
123
+ 'that contains markers created by the collaboration features.');
124
+ }
125
+ // Restricted Editing integration can also lead to problems. Warn the user accordingly.
126
+ if (editor.plugins.has('RestrictedEditingModeEditing')) {
127
+ console.warn('You initialized the editor with the source editing feature and restricted editing feature. ' +
128
+ 'Please be advised that the source editing feature may not work, and be careful when editing document source ' +
129
+ 'that contains markers created by the restricted editing feature.');
130
+ }
131
+ }
132
+ /**
133
+ * Updates the source data in all hidden editing roots.
134
+ */
135
+ updateEditorData() {
136
+ const editor = this.editor;
137
+ const data = {};
138
+ for (const [rootName, domSourceEditingElementWrapper] of this._replacedRoots) {
139
+ const oldData = this._dataFromRoots.get(rootName);
140
+ const newData = domSourceEditingElementWrapper.dataset.value;
141
+ // Do not set the data unless some changes have been made in the meantime.
142
+ // This prevents empty undo steps after switching to the normal editor.
143
+ if (oldData !== newData) {
144
+ data[rootName] = newData;
145
+ this._dataFromRoots.set(rootName, newData);
146
+ }
147
+ }
148
+ if (Object.keys(data).length) {
149
+ editor.data.set(data, { batchType: { isUndoable: true } });
150
+ }
151
+ }
152
+ /**
153
+ * Creates source editing wrappers that replace each editing root. Each wrapper contains the document source from the corresponding
154
+ * root.
155
+ *
156
+ * The wrapper element contains a textarea and it solves the problem, that the textarea element cannot auto expand its height based on
157
+ * 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
158
+ * needs to, in order to display the whole document source without scrolling. The wrapper element is a parent for the textarea and for
159
+ * the pseudo-element `::after`, that replicates the look, content, and position of the textarea. The pseudo-element replica is hidden,
160
+ * 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
161
+ * of its children (the textarea and the `::after` pseudo-element) are positioned within a CSS grid to occupy the same grid cell. The
162
+ * content in the pseudo-element `::after` is set in CSS and it stretches the grid to the appropriate size based on the textarea value.
163
+ * Since both children occupy the same grid cell, both have always the same height.
164
+ */
165
+ _showSourceEditing() {
166
+ const editor = this.editor;
167
+ const editingView = editor.editing.view;
168
+ const model = editor.model;
169
+ model.change(writer => {
170
+ writer.setSelection(null);
171
+ writer.removeSelectionAttribute(model.document.selection.getAttributeKeys());
172
+ });
173
+ // It is not needed to iterate through all editing roots, as currently the plugin supports only the Classic Editor with a single
174
+ // main root, but this code may help understand and use this feature in external integrations.
175
+ for (const [rootName, domRootElement] of editingView.domRoots) {
176
+ const data = formatSource(editor.data.get({ rootName }));
177
+ const domSourceEditingElementTextarea = createElement(domRootElement.ownerDocument, 'textarea', {
178
+ rows: '1',
179
+ 'aria-label': 'Source code editing area'
180
+ });
181
+ const domSourceEditingElementWrapper = createElement(domRootElement.ownerDocument, 'div', {
182
+ class: 'ck-source-editing-area',
183
+ 'data-value': data
184
+ }, [domSourceEditingElementTextarea]);
185
+ domSourceEditingElementTextarea.value = data;
186
+ // Setting a value to textarea moves the input cursor to the end. We want the selection at the beginning.
187
+ domSourceEditingElementTextarea.setSelectionRange(0, 0);
188
+ // Bind the textarea's value to the wrapper's `data-value` property. Each change of the textarea's value updates the
189
+ // wrapper's `data-value` property.
190
+ domSourceEditingElementTextarea.addEventListener('input', () => {
191
+ domSourceEditingElementWrapper.dataset.value = domSourceEditingElementTextarea.value;
192
+ editor.ui.update();
193
+ });
194
+ editingView.change(writer => {
195
+ const viewRoot = editingView.document.getRoot(rootName);
196
+ writer.addClass('ck-hidden', viewRoot);
197
+ });
198
+ // Register the element so it becomes available for Alt+F10 and Esc navigation.
199
+ editor.ui.setEditableElement('sourceEditing:' + rootName, domSourceEditingElementTextarea);
200
+ this._replacedRoots.set(rootName, domSourceEditingElementWrapper);
201
+ this._elementReplacer.replace(domRootElement, domSourceEditingElementWrapper);
202
+ this._dataFromRoots.set(rootName, data);
203
+ }
204
+ this._focusSourceEditing();
205
+ }
206
+ /**
207
+ * Restores all hidden editing roots and sets the source data in them.
208
+ */
209
+ _hideSourceEditing() {
210
+ const editor = this.editor;
211
+ const editingView = editor.editing.view;
212
+ this.updateEditorData();
213
+ editingView.change(writer => {
214
+ for (const [rootName] of this._replacedRoots) {
215
+ writer.removeClass('ck-hidden', editingView.document.getRoot(rootName));
216
+ }
217
+ });
218
+ this._elementReplacer.restore();
219
+ this._replacedRoots.clear();
220
+ this._dataFromRoots.clear();
221
+ editingView.focus();
222
+ }
223
+ /**
224
+ * Focuses the textarea containing document source from the first editing root.
225
+ */
226
+ _focusSourceEditing() {
227
+ const editor = this.editor;
228
+ const [domSourceEditingElementWrapper] = this._replacedRoots.values();
229
+ const textarea = domSourceEditingElementWrapper.querySelector('textarea');
230
+ // The FocusObserver was disabled by View.render() while the DOM root was getting hidden and the replacer
231
+ // revealed the textarea. So it couldn't notice that the DOM root got blurred in the process.
232
+ // Let's sync this state manually here because otherwise Renderer will attempt to render selection
233
+ // in an invisible DOM root.
234
+ editor.editing.view.document.isFocused = false;
235
+ textarea.focus();
236
+ }
237
+ /**
238
+ * Disables all commands.
239
+ */
240
+ _disableCommands() {
241
+ const editor = this.editor;
242
+ for (const command of editor.commands.commands()) {
243
+ command.forceDisabled(COMMAND_FORCE_DISABLE_ID);
244
+ }
245
+ }
246
+ /**
247
+ * Clears forced disable for all commands, that was previously set through {@link #_disableCommands}.
248
+ */
249
+ _enableCommands() {
250
+ const editor = this.editor;
251
+ for (const command of editor.commands.commands()) {
252
+ command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
253
+ }
254
+ }
255
+ /**
256
+ * Adds or removes the `readonly` attribute from the textarea from all roots, if document source mode is active.
257
+ *
258
+ * @param isReadOnly Indicates whether all textarea elements should be read-only.
259
+ */
260
+ _handleReadOnlyMode(isReadOnly) {
261
+ if (!this.isSourceEditingMode) {
262
+ return;
263
+ }
264
+ for (const [, domSourceEditingElementWrapper] of this._replacedRoots) {
265
+ domSourceEditingElementWrapper.querySelector('textarea').readOnly = isReadOnly;
266
+ }
267
+ }
268
+ /**
269
+ * Checks, if the plugin is allowed to handle the source editing mode by itself. Currently, the source editing mode is supported only
270
+ * for the {@link module:editor-classic/classiceditor~ClassicEditor classic editor}.
271
+ */
272
+ _isAllowedToHandleSourceEditingMode() {
273
+ const editor = this.editor;
274
+ const editable = editor.ui.view.editable;
275
+ // Checks, if the editor's editable belongs to the editor's DOM tree.
276
+ return editable && !editable.hasExternalElement;
277
+ }
278
+ }
279
+ /**
280
+ * Formats the content for a better readability.
281
+ *
282
+ * For a non-HTML source the unchanged input string is returned.
283
+ *
284
+ * @param input Input string to check.
285
+ */
286
+ function formatSource(input) {
287
+ if (!isHtml(input)) {
288
+ return input;
289
+ }
290
+ return formatHtml(input);
291
+ }
292
+ /**
293
+ * Checks, if the document source is HTML. It is sufficient to just check the first character from the document data.
294
+ *
295
+ * @param input Input string to check.
296
+ */
297
+ function isHtml(input) {
298
+ return input.startsWith('<');
299
+ }
@@ -1,19 +1,19 @@
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
- /**
6
- * @module source-editing/utils/formathtml
7
- */
8
- /**
9
- * A simple (and naive) HTML code formatter that returns a formatted HTML markup that can be easily
10
- * parsed by human eyes. It beautifies the HTML code by adding new lines between elements that behave like block elements
11
- * (https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements
12
- * and a few more like `tr`, `td`, and similar ones) and inserting indents for nested content.
13
- *
14
- * WARNING: This function works only on a text that does not contain any indentations or new lines.
15
- * Calling this function on the already formatted text will damage the formatting.
16
- *
17
- * @param input An HTML string to format.
18
- */
19
- export declare function formatHtml(input: string): string;
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
+ /**
6
+ * @module source-editing/utils/formathtml
7
+ */
8
+ /**
9
+ * A simple (and naive) HTML code formatter that returns a formatted HTML markup that can be easily
10
+ * parsed by human eyes. It beautifies the HTML code by adding new lines between elements that behave like block elements
11
+ * (https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements
12
+ * and a few more like `tr`, `td`, and similar ones) and inserting indents for nested content.
13
+ *
14
+ * WARNING: This function works only on a text that does not contain any indentations or new lines.
15
+ * Calling this function on the already formatted text will damage the formatting.
16
+ *
17
+ * @param input An HTML string to format.
18
+ */
19
+ export declare function formatHtml(input: string): string;