@ckeditor/ckeditor5-restricted-editing 36.0.1 → 37.0.0-alpha.1

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.
@@ -2,500 +2,390 @@
2
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
5
  /**
7
6
  * @module restricted-editing/restrictededitingmodeediting
8
7
  */
9
-
10
8
  import { Plugin } from 'ckeditor5/src/core';
11
-
12
- import RestrictedEditingNavigationCommand from './restrictededitingmodenavigationcommand';
13
- import {
14
- extendMarkerOnTypingPostFixer,
15
- resurrectCollapsedMarkerPostFixer,
16
- setupExceptionHighlighting,
17
- upcastHighlightToMarker
18
- } from './restrictededitingmode/converters';
9
+ import RestrictedEditingModeNavigationCommand from './restrictededitingmodenavigationcommand';
10
+ import { extendMarkerOnTypingPostFixer, resurrectCollapsedMarkerPostFixer, setupExceptionHighlighting, upcastHighlightToMarker } from './restrictededitingmode/converters';
19
11
  import { getMarkerAtPosition, isSelectionInMarker } from './restrictededitingmode/utils';
20
-
21
12
  const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
22
-
23
13
  /**
24
14
  * The restricted editing mode editing feature.
25
15
  *
26
16
  * * It introduces the exception marker group that renders to `<span>` elements with the `restricted-editing-exception` CSS class.
27
17
  * * It registers the `'goToPreviousRestrictedEditingException'` and `'goToNextRestrictedEditingException'` commands.
28
18
  * * It also enables highlighting exception markers that are selected.
29
- *
30
- * @extends module:core/plugin~Plugin
31
19
  */
32
20
  export default class RestrictedEditingModeEditing extends Plugin {
33
- /**
34
- * @inheritDoc
35
- */
36
- static get pluginName() {
37
- return 'RestrictedEditingModeEditing';
38
- }
39
-
40
- /**
41
- * @inheritDoc
42
- */
43
- constructor( editor ) {
44
- super( editor );
45
-
46
- editor.config.define( 'restrictedEditing', {
47
- allowedCommands: [ 'bold', 'italic', 'link', 'unlink' ],
48
- allowedAttributes: [ 'bold', 'italic', 'linkHref' ]
49
- } );
50
-
51
- /**
52
- * Command names that are enabled outside the non-restricted regions.
53
- *
54
- * @type {Set.<String>}
55
- * @private
56
- */
57
- this._alwaysEnabled = new Set( [ 'undo', 'redo' ] );
58
-
59
- /**
60
- * Commands allowed in non-restricted areas.
61
- *
62
- * Commands always enabled combine typing feature commands: `'input'`, `'insertText'`, `'delete'`, and `'deleteForward'` with
63
- * commands defined in the feature configuration.
64
- *
65
- * @type {Set<string>}
66
- * @private
67
- */
68
- this._allowedInException = new Set( [ 'input', 'insertText', 'delete', 'deleteForward' ] );
69
- }
70
-
71
- /**
72
- * @inheritDoc
73
- */
74
- init() {
75
- const editor = this.editor;
76
- const editingView = editor.editing.view;
77
- const allowedCommands = editor.config.get( 'restrictedEditing.allowedCommands' );
78
-
79
- allowedCommands.forEach( commandName => this._allowedInException.add( commandName ) );
80
-
81
- this._setupConversion();
82
- this._setupCommandsToggling();
83
- this._setupRestrictions();
84
-
85
- // Commands & keystrokes that allow navigation in the content.
86
- editor.commands.add( 'goToPreviousRestrictedEditingException', new RestrictedEditingNavigationCommand( editor, 'backward' ) );
87
- editor.commands.add( 'goToNextRestrictedEditingException', new RestrictedEditingNavigationCommand( editor, 'forward' ) );
88
- editor.keystrokes.set( 'Tab', getCommandExecuter( editor, 'goToNextRestrictedEditingException' ) );
89
- editor.keystrokes.set( 'Shift+Tab', getCommandExecuter( editor, 'goToPreviousRestrictedEditingException' ) );
90
- editor.keystrokes.set( 'Ctrl+A', getSelectAllHandler( editor ) );
91
-
92
- editingView.change( writer => {
93
- for ( const root of editingView.document.roots ) {
94
- writer.addClass( 'ck-restricted-editing_mode_restricted', root );
95
- }
96
- } );
97
- }
98
-
99
- /**
100
- * Makes the given command always enabled in the restricted editing mode (regardless
101
- * of selection location).
102
- *
103
- * To enable some commands in non-restricted areas of the content use
104
- * {@link module:restricted-editing/restrictededitingmode~RestrictedEditingModeConfig#allowedCommands} configuration option.
105
- *
106
- * @param {String} commandName Name of the command to enable.
107
- */
108
- enableCommand( commandName ) {
109
- const command = this.editor.commands.get( commandName );
110
-
111
- command.clearForceDisabled( COMMAND_FORCE_DISABLE_ID );
112
-
113
- this._alwaysEnabled.add( commandName );
114
- }
115
-
116
- /**
117
- * Sets up the restricted mode editing conversion:
118
- *
119
- * * ucpast & downcast converters,
120
- * * marker highlighting in the edting area,
121
- * * marker post-fixers.
122
- *
123
- * @private
124
- */
125
- _setupConversion() {
126
- const editor = this.editor;
127
- const model = editor.model;
128
- const doc = model.document;
129
-
130
- // The restricted editing does not attach additional data to the zones so there's no need for smarter markers managing.
131
- // Also, the markers will only be created when loading the data.
132
- let markerNumber = 0;
133
-
134
- editor.conversion.for( 'upcast' ).add( upcastHighlightToMarker( {
135
- view: {
136
- name: 'span',
137
- classes: 'restricted-editing-exception'
138
- },
139
- model: () => {
140
- markerNumber++; // Starting from restrictedEditingException:1 marker.
141
-
142
- return `restrictedEditingException:${ markerNumber }`;
143
- }
144
- } ) );
145
-
146
- // Currently the marker helpers are tied to other use-cases and do not render a collapsed marker as highlight.
147
- // That's why there are 2 downcast converters for them:
148
- // 1. The default marker-to-highlight will wrap selected text with `<span>`.
149
- editor.conversion.for( 'downcast' ).markerToHighlight( {
150
- model: 'restrictedEditingException',
151
- // Use callback to return new object every time new marker instance is created - otherwise it will be seen as the same marker.
152
- view: () => {
153
- return {
154
- name: 'span',
155
- classes: 'restricted-editing-exception',
156
- priority: -10
157
- };
158
- }
159
- } );
160
-
161
- // 2. But for collapsed marker we need to render it as an element.
162
- // Additionally the editing pipeline should always display a collapsed marker.
163
- editor.conversion.for( 'editingDowncast' ).markerToElement( {
164
- model: 'restrictedEditingException',
165
- view: ( markerData, { writer } ) => {
166
- return writer.createUIElement( 'span', {
167
- class: 'restricted-editing-exception restricted-editing-exception_collapsed'
168
- } );
169
- }
170
- } );
171
-
172
- editor.conversion.for( 'dataDowncast' ).markerToElement( {
173
- model: 'restrictedEditingException',
174
- view: ( markerData, { writer } ) => {
175
- return writer.createEmptyElement( 'span', {
176
- class: 'restricted-editing-exception'
177
- } );
178
- }
179
- } );
180
-
181
- doc.registerPostFixer( extendMarkerOnTypingPostFixer( editor ) );
182
- doc.registerPostFixer( resurrectCollapsedMarkerPostFixer( editor ) );
183
- doc.registerPostFixer( ensureNewMarkerIsFlatPostFixer( editor ) );
184
-
185
- setupExceptionHighlighting( editor );
186
- }
187
-
188
- /**
189
- * Setups additional editing restrictions beyond command toggling:
190
- *
191
- * * delete content range trimming
192
- * * disabling input command outside exception marker
193
- * * restricting clipboard holder to text only
194
- * * restricting text attributes in content
195
- *
196
- * @private
197
- */
198
- _setupRestrictions() {
199
- const editor = this.editor;
200
- const model = editor.model;
201
- const selection = model.document.selection;
202
- const viewDoc = editor.editing.view.document;
203
- const clipboard = editor.plugins.get( 'ClipboardPipeline' );
204
-
205
- this.listenTo( model, 'deleteContent', restrictDeleteContent( editor ), { priority: 'high' } );
206
-
207
- const inputCommand = editor.commands.get( 'input' );
208
- const insertTextCommand = editor.commands.get( 'insertText' );
209
-
210
- // The restricted editing might be configured without input support - ie allow only bolding or removing text.
211
- // This check is bit synthetic since only tests are used this way.
212
- if ( inputCommand ) {
213
- this.listenTo( inputCommand, 'execute', disallowInputExecForWrongRange( editor ), { priority: 'high' } );
214
- }
215
-
216
- // The restricted editing might be configured without insert text support - ie allow only bolding or removing text.
217
- // This check is bit synthetic since only tests are used this way.
218
- if ( insertTextCommand ) {
219
- this.listenTo( insertTextCommand, 'execute', disallowInputExecForWrongRange( editor ), { priority: 'high' } );
220
- }
221
-
222
- // Block clipboard outside exception marker on paste and drop.
223
- this.listenTo( clipboard, 'contentInsertion', evt => {
224
- if ( !isRangeInsideSingleMarker( editor, selection.getFirstRange() ) ) {
225
- evt.stop();
226
- }
227
- } );
228
-
229
- // Block clipboard outside exception marker on cut.
230
- this.listenTo( viewDoc, 'clipboardOutput', ( evt, data ) => {
231
- if ( data.method == 'cut' && !isRangeInsideSingleMarker( editor, selection.getFirstRange() ) ) {
232
- evt.stop();
233
- }
234
- }, { priority: 'high' } );
235
-
236
- const allowedAttributes = editor.config.get( 'restrictedEditing.allowedAttributes' );
237
- model.schema.addAttributeCheck( onlyAllowAttributesFromList( allowedAttributes ) );
238
- model.schema.addChildCheck( allowTextOnlyInClipboardHolder );
239
- }
240
-
241
- /**
242
- * Sets up the command toggling which enables or disables commands based on the user selection.
243
- *
244
- * @private
245
- */
246
- _setupCommandsToggling() {
247
- const editor = this.editor;
248
- const model = editor.model;
249
- const doc = model.document;
250
-
251
- this._disableCommands();
252
-
253
- this.listenTo( doc.selection, 'change', this._checkCommands.bind( this ) );
254
- this.listenTo( doc, 'change:data', this._checkCommands.bind( this ) );
255
- }
256
-
257
- /**
258
- * Checks if commands should be enabled or disabled based on the current selection.
259
- *
260
- * @private
261
- */
262
- _checkCommands() {
263
- const editor = this.editor;
264
- const selection = editor.model.document.selection;
265
-
266
- if ( selection.rangeCount > 1 ) {
267
- this._disableCommands();
268
-
269
- return;
270
- }
271
-
272
- const marker = getMarkerAtPosition( editor, selection.focus );
273
-
274
- this._disableCommands();
275
-
276
- if ( isSelectionInMarker( selection, marker ) ) {
277
- this._enableCommands( marker );
278
- }
279
- }
280
-
281
- /**
282
- * Enables commands in non-restricted regions.
283
- *
284
- * @returns {module:engine/model/markercollection~Marker} marker
285
- * @private
286
- */
287
- _enableCommands( marker ) {
288
- const editor = this.editor;
289
-
290
- for ( const [ commandName, command ] of editor.commands ) {
291
- if ( !command.affectsData || this._alwaysEnabled.has( commandName ) ) {
292
- continue;
293
- }
294
-
295
- // Enable ony those commands that are allowed in the exception marker.
296
- if ( !this._allowedInException.has( commandName ) ) {
297
- continue;
298
- }
299
-
300
- // Do not enable 'delete' and 'deleteForward' commands on the exception marker boundaries.
301
- if ( isDeleteCommandOnMarkerBoundaries( commandName, editor.model.document.selection, marker.getRange() ) ) {
302
- continue;
303
- }
304
-
305
- command.clearForceDisabled( COMMAND_FORCE_DISABLE_ID );
306
- }
307
- }
308
-
309
- /**
310
- * Disables commands outside non-restricted regions.
311
- *
312
- * @private
313
- */
314
- _disableCommands() {
315
- const editor = this.editor;
316
-
317
- for ( const [ commandName, command ] of editor.commands ) {
318
- if ( !command.affectsData || this._alwaysEnabled.has( commandName ) ) {
319
- continue;
320
- }
321
-
322
- command.forceDisabled( COMMAND_FORCE_DISABLE_ID );
323
- }
324
- }
21
+ /**
22
+ * @inheritDoc
23
+ */
24
+ static get pluginName() {
25
+ return 'RestrictedEditingModeEditing';
26
+ }
27
+ /**
28
+ * @inheritDoc
29
+ */
30
+ constructor(editor) {
31
+ super(editor);
32
+ editor.config.define('restrictedEditing', {
33
+ allowedCommands: ['bold', 'italic', 'link', 'unlink'],
34
+ allowedAttributes: ['bold', 'italic', 'linkHref']
35
+ });
36
+ this._alwaysEnabled = new Set(['undo', 'redo']);
37
+ this._allowedInException = new Set(['input', 'insertText', 'delete', 'deleteForward']);
38
+ }
39
+ /**
40
+ * @inheritDoc
41
+ */
42
+ init() {
43
+ const editor = this.editor;
44
+ const editingView = editor.editing.view;
45
+ const allowedCommands = editor.config.get('restrictedEditing.allowedCommands');
46
+ allowedCommands.forEach(commandName => this._allowedInException.add(commandName));
47
+ this._setupConversion();
48
+ this._setupCommandsToggling();
49
+ this._setupRestrictions();
50
+ // Commands & keystrokes that allow navigation in the content.
51
+ editor.commands.add('goToPreviousRestrictedEditingException', new RestrictedEditingModeNavigationCommand(editor, 'backward'));
52
+ editor.commands.add('goToNextRestrictedEditingException', new RestrictedEditingModeNavigationCommand(editor, 'forward'));
53
+ editor.keystrokes.set('Tab', getCommandExecuter(editor, 'goToNextRestrictedEditingException'));
54
+ editor.keystrokes.set('Shift+Tab', getCommandExecuter(editor, 'goToPreviousRestrictedEditingException'));
55
+ editor.keystrokes.set('Ctrl+A', getSelectAllHandler(editor));
56
+ editingView.change(writer => {
57
+ for (const root of editingView.document.roots) {
58
+ writer.addClass('ck-restricted-editing_mode_restricted', root);
59
+ }
60
+ });
61
+ }
62
+ /**
63
+ * Makes the given command always enabled in the restricted editing mode (regardless
64
+ * of selection location).
65
+ *
66
+ * To enable some commands in non-restricted areas of the content use
67
+ * {@link module:restricted-editing/restrictededitingconfig~RestrictedEditingConfig#allowedCommands} configuration option.
68
+ *
69
+ * @param commandName Name of the command to enable.
70
+ */
71
+ enableCommand(commandName) {
72
+ const command = this.editor.commands.get(commandName);
73
+ command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
74
+ this._alwaysEnabled.add(commandName);
75
+ }
76
+ /**
77
+ * Sets up the restricted mode editing conversion:
78
+ *
79
+ * * ucpast & downcast converters,
80
+ * * marker highlighting in the edting area,
81
+ * * marker post-fixers.
82
+ */
83
+ _setupConversion() {
84
+ const editor = this.editor;
85
+ const model = editor.model;
86
+ const doc = model.document;
87
+ // The restricted editing does not attach additional data to the zones so there's no need for smarter markers managing.
88
+ // Also, the markers will only be created when loading the data.
89
+ let markerNumber = 0;
90
+ editor.conversion.for('upcast').add(upcastHighlightToMarker({
91
+ view: {
92
+ name: 'span',
93
+ classes: 'restricted-editing-exception'
94
+ },
95
+ model: () => {
96
+ markerNumber++; // Starting from restrictedEditingException:1 marker.
97
+ return `restrictedEditingException:${markerNumber}`;
98
+ }
99
+ }));
100
+ // Currently the marker helpers are tied to other use-cases and do not render a collapsed marker as highlight.
101
+ // That's why there are 2 downcast converters for them:
102
+ // 1. The default marker-to-highlight will wrap selected text with `<span>`.
103
+ editor.conversion.for('downcast').markerToHighlight({
104
+ model: 'restrictedEditingException',
105
+ // Use callback to return new object every time new marker instance is created - otherwise it will be seen as the same marker.
106
+ view: () => {
107
+ return {
108
+ name: 'span',
109
+ classes: 'restricted-editing-exception',
110
+ priority: -10
111
+ };
112
+ }
113
+ });
114
+ // 2. But for collapsed marker we need to render it as an element.
115
+ // Additionally the editing pipeline should always display a collapsed marker.
116
+ editor.conversion.for('editingDowncast').markerToElement({
117
+ model: 'restrictedEditingException',
118
+ view: (markerData, { writer }) => {
119
+ return writer.createUIElement('span', {
120
+ class: 'restricted-editing-exception restricted-editing-exception_collapsed'
121
+ });
122
+ }
123
+ });
124
+ editor.conversion.for('dataDowncast').markerToElement({
125
+ model: 'restrictedEditingException',
126
+ view: (markerData, { writer }) => {
127
+ return writer.createEmptyElement('span', {
128
+ class: 'restricted-editing-exception'
129
+ });
130
+ }
131
+ });
132
+ doc.registerPostFixer(extendMarkerOnTypingPostFixer(editor));
133
+ doc.registerPostFixer(resurrectCollapsedMarkerPostFixer(editor));
134
+ doc.registerPostFixer(ensureNewMarkerIsFlatPostFixer(editor));
135
+ setupExceptionHighlighting(editor);
136
+ }
137
+ /**
138
+ * Setups additional editing restrictions beyond command toggling:
139
+ *
140
+ * * delete content range trimming
141
+ * * disabling input command outside exception marker
142
+ * * restricting clipboard holder to text only
143
+ * * restricting text attributes in content
144
+ */
145
+ _setupRestrictions() {
146
+ const editor = this.editor;
147
+ const model = editor.model;
148
+ const selection = model.document.selection;
149
+ const viewDoc = editor.editing.view.document;
150
+ const clipboard = editor.plugins.get('ClipboardPipeline');
151
+ this.listenTo(model, 'deleteContent', restrictDeleteContent(editor), { priority: 'high' });
152
+ const insertTextCommand = editor.commands.get('insertText');
153
+ // The restricted editing might be configured without insert text support - ie allow only bolding or removing text.
154
+ // This check is bit synthetic since only tests are used this way.
155
+ if (insertTextCommand) {
156
+ this.listenTo(insertTextCommand, 'execute', disallowInputExecForWrongRange(editor), { priority: 'high' });
157
+ }
158
+ // Block clipboard outside exception marker on paste and drop.
159
+ this.listenTo(clipboard, 'contentInsertion', evt => {
160
+ if (!isRangeInsideSingleMarker(editor, selection.getFirstRange())) {
161
+ evt.stop();
162
+ }
163
+ });
164
+ // Block clipboard outside exception marker on cut.
165
+ this.listenTo(viewDoc, 'clipboardOutput', (evt, data) => {
166
+ if (data.method == 'cut' && !isRangeInsideSingleMarker(editor, selection.getFirstRange())) {
167
+ evt.stop();
168
+ }
169
+ }, { priority: 'high' });
170
+ const allowedAttributes = editor.config.get('restrictedEditing.allowedAttributes');
171
+ model.schema.addAttributeCheck(onlyAllowAttributesFromList(allowedAttributes));
172
+ model.schema.addChildCheck(allowTextOnlyInClipboardHolder());
173
+ }
174
+ /**
175
+ * Sets up the command toggling which enables or disables commands based on the user selection.
176
+ */
177
+ _setupCommandsToggling() {
178
+ const editor = this.editor;
179
+ const model = editor.model;
180
+ const doc = model.document;
181
+ this._disableCommands();
182
+ this.listenTo(doc.selection, 'change', this._checkCommands.bind(this));
183
+ this.listenTo(doc, 'change:data', this._checkCommands.bind(this));
184
+ }
185
+ /**
186
+ * Checks if commands should be enabled or disabled based on the current selection.
187
+ */
188
+ _checkCommands() {
189
+ const editor = this.editor;
190
+ const selection = editor.model.document.selection;
191
+ if (selection.rangeCount > 1) {
192
+ this._disableCommands();
193
+ return;
194
+ }
195
+ const marker = getMarkerAtPosition(editor, selection.focus);
196
+ this._disableCommands();
197
+ if (isSelectionInMarker(selection, marker)) {
198
+ this._enableCommands(marker);
199
+ }
200
+ }
201
+ /**
202
+ * Enables commands in non-restricted regions.
203
+ */
204
+ _enableCommands(marker) {
205
+ const editor = this.editor;
206
+ for (const [commandName, command] of editor.commands) {
207
+ if (!command.affectsData || this._alwaysEnabled.has(commandName)) {
208
+ continue;
209
+ }
210
+ // Enable ony those commands that are allowed in the exception marker.
211
+ if (!this._allowedInException.has(commandName)) {
212
+ continue;
213
+ }
214
+ // Do not enable 'delete' and 'deleteForward' commands on the exception marker boundaries.
215
+ if (isDeleteCommandOnMarkerBoundaries(commandName, editor.model.document.selection, marker.getRange())) {
216
+ continue;
217
+ }
218
+ command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
219
+ }
220
+ }
221
+ /**
222
+ * Disables commands outside non-restricted regions.
223
+ */
224
+ _disableCommands() {
225
+ const editor = this.editor;
226
+ for (const [commandName, command] of editor.commands) {
227
+ if (!command.affectsData || this._alwaysEnabled.has(commandName)) {
228
+ continue;
229
+ }
230
+ command.forceDisabled(COMMAND_FORCE_DISABLE_ID);
231
+ }
232
+ }
325
233
  }
326
-
327
- // Helper method for executing enabled commands only.
328
- function getCommandExecuter( editor, commandName ) {
329
- return ( data, cancel ) => {
330
- const command = editor.commands.get( commandName );
331
-
332
- if ( command.isEnabled ) {
333
- editor.execute( commandName );
334
- cancel();
335
- }
336
- };
234
+ /**
235
+ * Helper method for executing enabled commands only.
236
+ */
237
+ function getCommandExecuter(editor, commandName) {
238
+ return (_, cancel) => {
239
+ const command = editor.commands.get(commandName);
240
+ if (command.isEnabled) {
241
+ editor.execute(commandName);
242
+ cancel();
243
+ }
244
+ };
337
245
  }
338
-
339
- // Helper for handling Ctrl+A keydown behaviour.
340
- function getSelectAllHandler( editor ) {
341
- return ( data, cancel ) => {
342
- const model = editor.model;
343
- const selection = editor.model.document.selection;
344
- const marker = getMarkerAtPosition( editor, selection.focus );
345
-
346
- if ( !marker ) {
347
- return;
348
- }
349
-
350
- // If selection range is inside a restricted editing exception, select text only within the exception.
351
- //
352
- // Note: Second Ctrl+A press is also blocked and it won't select the entire text in the editor.
353
- const selectionRange = selection.getFirstRange();
354
- const markerRange = marker.getRange();
355
-
356
- if ( markerRange.containsRange( selectionRange, true ) || selection.isCollapsed ) {
357
- cancel();
358
-
359
- model.change( writer => {
360
- writer.setSelection( marker.getRange() );
361
- } );
362
- }
363
- };
246
+ /**
247
+ * Helper for handling Ctrl+A keydown behaviour.
248
+ */
249
+ function getSelectAllHandler(editor) {
250
+ return (_, cancel) => {
251
+ const model = editor.model;
252
+ const selection = editor.model.document.selection;
253
+ const marker = getMarkerAtPosition(editor, selection.focus);
254
+ if (!marker) {
255
+ return;
256
+ }
257
+ // If selection range is inside a restricted editing exception, select text only within the exception.
258
+ //
259
+ // Note: Second Ctrl+A press is also blocked and it won't select the entire text in the editor.
260
+ const selectionRange = selection.getFirstRange();
261
+ const markerRange = marker.getRange();
262
+ if (markerRange.containsRange(selectionRange, true) || selection.isCollapsed) {
263
+ cancel();
264
+ model.change(writer => {
265
+ writer.setSelection(marker.getRange());
266
+ });
267
+ }
268
+ };
364
269
  }
365
-
366
- // Additional rule for enabling "delete" and "deleteForward" commands if selection is on range boundaries:
367
- //
368
- // Does not allow to enable command when selection focus is:
369
- // - is on marker start - "delete" - to prevent removing content before marker
370
- // - is on marker end - "deleteForward" - to prevent removing content after marker
371
- function isDeleteCommandOnMarkerBoundaries( commandName, selection, markerRange ) {
372
- if ( commandName == 'delete' && markerRange.start.isEqual( selection.focus ) ) {
373
- return true;
374
- }
375
-
376
- // Only for collapsed selection - non-collapsed selection that extends over a marker is handled elsewhere.
377
- if ( commandName == 'deleteForward' && selection.isCollapsed && markerRange.end.isEqual( selection.focus ) ) {
378
- return true;
379
- }
380
-
381
- return false;
270
+ /**
271
+ * Additional rule for enabling "delete" and "deleteForward" commands if selection is on range boundaries:
272
+ *
273
+ * Does not allow to enable command when selection focus is:
274
+ * - is on marker start - "delete" - to prevent removing content before marker
275
+ * - is on marker end - "deleteForward" - to prevent removing content after marker
276
+ */
277
+ function isDeleteCommandOnMarkerBoundaries(commandName, selection, markerRange) {
278
+ if (commandName == 'delete' && markerRange.start.isEqual(selection.focus)) {
279
+ return true;
280
+ }
281
+ // Only for collapsed selection - non-collapsed selection that extends over a marker is handled elsewhere.
282
+ if (commandName == 'deleteForward' && selection.isCollapsed && markerRange.end.isEqual(selection.focus)) {
283
+ return true;
284
+ }
285
+ return false;
382
286
  }
383
-
384
- // Ensures that model.deleteContent() does not delete outside exception markers ranges.
385
- //
386
- // The enforced restrictions are:
387
- // - only execute deleteContent() inside exception markers
388
- // - restrict passed selection to exception marker
389
- function restrictDeleteContent( editor ) {
390
- return ( evt, args ) => {
391
- const [ selection ] = args;
392
-
393
- const marker = getMarkerAtPosition( editor, selection.focus ) || getMarkerAtPosition( editor, selection.anchor );
394
-
395
- // Stop method execution if marker was not found at selection focus.
396
- if ( !marker ) {
397
- evt.stop();
398
-
399
- return;
400
- }
401
-
402
- // Collapsed selection inside exception marker does not require fixing.
403
- if ( selection.isCollapsed ) {
404
- return;
405
- }
406
-
407
- // Shrink the selection to the range inside exception marker.
408
- const allowedToDelete = marker.getRange().getIntersection( selection.getFirstRange() );
409
-
410
- // Some features uses selection passed to model.deleteContent() to set the selection afterwards. For this we need to properly modify
411
- // either the document selection using change block...
412
- if ( selection.is( 'documentSelection' ) ) {
413
- editor.model.change( writer => {
414
- writer.setSelection( allowedToDelete );
415
- } );
416
- }
417
- // ... or by modifying passed selection instance directly.
418
- else {
419
- selection.setTo( allowedToDelete );
420
- }
421
- };
287
+ /**
288
+ * Ensures that model.deleteContent() does not delete outside exception markers ranges.
289
+ *
290
+ * The enforced restrictions are:
291
+ * - only execute deleteContent() inside exception markers
292
+ * - restrict passed selection to exception marker
293
+ */
294
+ function restrictDeleteContent(editor) {
295
+ return (evt, args) => {
296
+ const [selection] = args;
297
+ const marker = getMarkerAtPosition(editor, selection.focus) || getMarkerAtPosition(editor, selection.anchor);
298
+ // Stop method execution if marker was not found at selection focus.
299
+ if (!marker) {
300
+ evt.stop();
301
+ return;
302
+ }
303
+ // Collapsed selection inside exception marker does not require fixing.
304
+ if (selection.isCollapsed) {
305
+ return;
306
+ }
307
+ // Shrink the selection to the range inside exception marker.
308
+ const allowedToDelete = marker.getRange().getIntersection(selection.getFirstRange());
309
+ // Some features uses selection passed to model.deleteContent() to set the selection afterwards. For this we need to properly modify
310
+ // either the document selection using change block...
311
+ if (selection.is('documentSelection')) {
312
+ editor.model.change(writer => {
313
+ writer.setSelection(allowedToDelete);
314
+ });
315
+ }
316
+ // ... or by modifying passed selection instance directly.
317
+ else {
318
+ selection.setTo(allowedToDelete);
319
+ }
320
+ };
422
321
  }
423
-
424
- // Ensures that input command is executed with a range that is inside exception marker.
425
- //
426
- // This restriction is due to fact that using native spell check changes text outside exception marker.
427
- function disallowInputExecForWrongRange( editor ) {
428
- return ( evt, args ) => {
429
- const [ options ] = args;
430
- const { range } = options;
431
-
432
- // Only check "input" command executed with a range value.
433
- // Selection might be set in exception marker but passed range might point elsewhere.
434
- if ( !range ) {
435
- return;
436
- }
437
-
438
- if ( !isRangeInsideSingleMarker( editor, range ) ) {
439
- evt.stop();
440
- }
441
- };
322
+ /**
323
+ * Ensures that input command is executed with a range that is inside exception marker.
324
+ *
325
+ * This restriction is due to fact that using native spell check changes text outside exception marker.
326
+ */
327
+ function disallowInputExecForWrongRange(editor) {
328
+ return (evt, args) => {
329
+ const [options] = args;
330
+ const { range } = options;
331
+ // Only check "input" command executed with a range value.
332
+ // Selection might be set in exception marker but passed range might point elsewhere.
333
+ if (!range) {
334
+ return;
335
+ }
336
+ if (!isRangeInsideSingleMarker(editor, range)) {
337
+ evt.stop();
338
+ }
339
+ };
442
340
  }
443
-
444
- function isRangeInsideSingleMarker( editor, range ) {
445
- const markerAtStart = getMarkerAtPosition( editor, range.start );
446
- const markerAtEnd = getMarkerAtPosition( editor, range.end );
447
-
448
- return markerAtStart && markerAtEnd && markerAtEnd === markerAtStart;
341
+ function isRangeInsideSingleMarker(editor, range) {
342
+ const markerAtStart = getMarkerAtPosition(editor, range.start);
343
+ const markerAtEnd = getMarkerAtPosition(editor, range.end);
344
+ return markerAtStart && markerAtEnd && markerAtEnd === markerAtStart;
449
345
  }
450
-
451
- // Checks if new marker range is flat. Non-flat ranges might appear during upcast conversion in nested structures, ie tables.
452
- //
453
- // Note: This marker fixer only consider case which is possible to create using StandardEditing mode plugin.
454
- // Markers created by developer in the data might break in many other ways.
455
- //
456
- // See #6003.
457
- function ensureNewMarkerIsFlatPostFixer( editor ) {
458
- return writer => {
459
- let changeApplied = false;
460
-
461
- const changedMarkers = editor.model.document.differ.getChangedMarkers();
462
-
463
- for ( const { data: { newRange, oldRange }, name } of changedMarkers ) {
464
- if ( !name.startsWith( 'restrictedEditingException' ) ) {
465
- continue;
466
- }
467
-
468
- if ( !oldRange && !newRange.isFlat ) {
469
- const start = newRange.start;
470
- const end = newRange.end;
471
-
472
- const startIsHigherInTree = start.path.length > end.path.length;
473
-
474
- const fixedStart = startIsHigherInTree ? newRange.start : writer.createPositionAt( end.parent, 0 );
475
- const fixedEnd = startIsHigherInTree ? writer.createPositionAt( start.parent, 'end' ) : newRange.end;
476
-
477
- writer.updateMarker( name, {
478
- range: writer.createRange( fixedStart, fixedEnd )
479
- } );
480
-
481
- changeApplied = true;
482
- }
483
- }
484
-
485
- return changeApplied;
486
- };
346
+ /**
347
+ * Checks if new marker range is flat. Non-flat ranges might appear during upcast conversion in nested structures, ie tables.
348
+ *
349
+ * Note: This marker fixer only consider case which is possible to create using StandardEditing mode plugin.
350
+ * Markers created by developer in the data might break in many other ways.
351
+ *
352
+ * See #6003.
353
+ */
354
+ function ensureNewMarkerIsFlatPostFixer(editor) {
355
+ return writer => {
356
+ let changeApplied = false;
357
+ const changedMarkers = editor.model.document.differ.getChangedMarkers();
358
+ for (const { data, name } of changedMarkers) {
359
+ if (!name.startsWith('restrictedEditingException')) {
360
+ continue;
361
+ }
362
+ const newRange = data.newRange;
363
+ if (!data.oldRange && !newRange.isFlat) {
364
+ const start = newRange.start;
365
+ const end = newRange.end;
366
+ const startIsHigherInTree = start.path.length > end.path.length;
367
+ const fixedStart = startIsHigherInTree ? newRange.start : writer.createPositionAt(end.parent, 0);
368
+ const fixedEnd = startIsHigherInTree ? writer.createPositionAt(start.parent, 'end') : newRange.end;
369
+ writer.updateMarker(name, {
370
+ range: writer.createRange(fixedStart, fixedEnd)
371
+ });
372
+ changeApplied = true;
373
+ }
374
+ }
375
+ return changeApplied;
376
+ };
487
377
  }
488
-
489
- function onlyAllowAttributesFromList( allowedAttributes ) {
490
- return ( context, attributeName ) => {
491
- if ( context.startsWith( '$clipboardHolder' ) ) {
492
- return allowedAttributes.includes( attributeName );
493
- }
494
- };
378
+ function onlyAllowAttributesFromList(allowedAttributes) {
379
+ return (context, attributeName) => {
380
+ if (context.startsWith('$clipboardHolder')) {
381
+ return allowedAttributes.includes(attributeName);
382
+ }
383
+ };
495
384
  }
496
-
497
- function allowTextOnlyInClipboardHolder( context, childDefinition ) {
498
- if ( context.startsWith( '$clipboardHolder' ) ) {
499
- return childDefinition.name === '$text';
500
- }
385
+ function allowTextOnlyInClipboardHolder() {
386
+ return (context, childDefinition) => {
387
+ if (context.startsWith('$clipboardHolder')) {
388
+ return childDefinition.name === '$text';
389
+ }
390
+ };
501
391
  }